{"id":2291,"date":"2024-04-29T15:07:56","date_gmt":"2024-04-29T13:07:56","guid":{"rendered":"https:\/\/blog.kodono.info\/wordpress\/?p=2291"},"modified":"2024-04-29T15:07:56","modified_gmt":"2024-04-29T13:07:56","slug":"connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bearer-token","status":"publish","type":"post","link":"https:\/\/blog.kodono.info\/wordpress\/2024\/04\/29\/connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bearer-token\/","title":{"rendered":"Connect to SharePoint Online from NodeJS using an impersonate (application) Bearer Token"},"content":{"rendered":"<p>(duplicate of this <a href=\"https:\/\/stackoverflow.com\/questions\/78402212\/connect-to-sharepoint-online-from-nodejs-using-an-impersonate-application-bear\/78402213#78402213\">StackOverflow answer<\/a>)<\/p>\n<h1 id=\"start-a-new-project\">Start a new project<\/h1>\n<ol>\n<li>In a new folder, type <code>npm init<\/code> to start a new project.<\/li>\n<li>Make sure to use a Node &gt;= v14 (I use Node v18 \u2013 and <a href=\"https:\/\/volta.sh\/\">Volta<\/a> can be useful to manage several versions of Node for Windows) <\/li>\n<li>Install some dependencies: <code>npm install axios @azure\/msal-node uuid<\/code>  <\/li>\n<\/ol>\n<h1 id=\"certificate\">Certificate<\/h1>\n<p>In the past, we could use the add-in application feature to get a token, but Microsoft <a href=\"https:\/\/learn.microsoft.com\/fr-fr\/sharepoint\/dev\/sp-add-ins\/retirement-announcement-for-add-ins\">announced<\/a> it will be retired.<\/p>\n<p>We now need to pass through an Azure application to get it. But before creating the Azure app, we need to create a Private Certificate key.<\/p>\n<ol>\n<li>Create the file <code>Create-SelfSignedCertificate.ps1<\/code> using the below code (<a href=\"https:\/\/learn.microsoft.com\/en-us\/sharepoint\/dev\/solution-guidance\/security-apponly-azuread#setting-up-an-azure-ad-app-for-app-only-access\">source<\/a>):   <\/li>\n<\/ol>\n<pre class=\"brush:powershell\">#Requires -RunAsAdministrator\r\n<#\r\n.SYNOPSIS\r\nCreates a Self Signed Certificate for use in server to server authentication\r\n.DESCRIPTION\r\nSource: https:\/\/learn.microsoft.com\/en-us\/sharepoint\/dev\/solution-guidance\/security-apponly-azuread#setting-up-an-azure-ad-app-for-app-only-access\r\n.EXAMPLE\r\n.\\Create-SelfSignedCertificate.ps1 -CommonName \"MyCert\" -StartDate 2015-11-21 -EndDate 2017-11-21\r\nThis will create a new self signed certificate with the common name \"CN=MyCert\". During creation you will be asked to provide a password to protect the private key.\r\n.EXAMPLE\r\n.\\Create-SelfSignedCertificate.ps1 -CommonName \"MyCert\" -StartDate 2015-11-21 -EndDate 2017-11-21 -Password (ConvertTo-SecureString -String \"MyPassword\" -AsPlainText -Force)\r\nThis will create a new self signed certificate with the common name \"CN=MyCert\". The password as specified in the Password parameter will be used to protect the private key\r\n.EXAMPLE\r\n.\\Create-SelfSignedCertificate.ps1 -CommonName \"MyCert\" -StartDate 2015-11-21 -EndDate 2017-11-21 -Force\r\nThis will create a new self signed certificate with the common name \"CN=MyCert\". During creation you will be asked to provide a password to protect the private key. If there is already a certificate with the common name you specified, it will be removed first.\r\n#>\r\nParam(\r\n\r\n[Parameter(Mandatory=$true)]\r\n   [string]$CommonName,\r\n\r\n[Parameter(Mandatory=$true)]\r\n   [DateTime]$StartDate,\r\n\r\n[Parameter(Mandatory=$true)]\r\n   [DateTime]$EndDate,\r\n\r\n[Parameter(Mandatory=$false, HelpMessage=\"Will overwrite existing certificates\")]\r\n   [Switch]$Force,\r\n\r\n[Parameter(Mandatory=$false)]\r\n   [SecureString]$Password\r\n)\r\n\r\n# DO NOT MODIFY BELOW\r\nfunction CreateSelfSignedCertificate(){\r\n  #Remove and existing certificates with the same common name from personal and root stores\r\n    #Need to be very wary of this as could break something\r\n    if($CommonName.ToLower().StartsWith(\"cn=\"))\r\n    {\r\n        # Remove CN from common name\r\n        $CommonName = $CommonName.Substring(3)\r\n    }\r\n    $certs = Get-ChildItem -Path Cert:\\LocalMachine\\my | Where-Object{$_.Subject -eq \"CN=$CommonName\"}\r\n    if($certs -ne $null -and $certs.Length -gt 0)\r\n    {\r\n        if($Force)\r\n        {\r\n\r\nforeach($c in $certs)\r\n            {\r\n                remove-item $c.PSPath\r\n            }\r\n        } else {\r\n            Write-Host -ForegroundColor Red \"One or more certificates with the same common name (CN=$CommonName) are already located in the local certificate store. Use -Force to remove them\";\r\n            return $false\r\n        }\r\n    }\r\n\r\n$name = new-object -com \"X509Enrollment.CX500DistinguishedName.1\"\r\n    $name.Encode(\"CN=$CommonName\", 0)\r\n\r\n$key = new-object -com \"X509Enrollment.CX509PrivateKey.1\"\r\n    $key.ProviderName = \"Microsoft RSA SChannel Cryptographic Provider\"\r\n    $key.KeySpec = 1\r\n    $key.Length = 2048\r\n    $key.SecurityDescriptor = \"D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)\"\r\n    $key.MachineContext = 1\r\n    $key.ExportPolicy = 1 # This is required to allow the private key to be exported\r\n    $key.Create()\r\n\r\n$serverauthoid = new-object -com \"X509Enrollment.CObjectId.1\"\r\n    $serverauthoid.InitializeFromValue(\"1.3.6.1.5.5.7.3.1\") # Server Authentication\r\n    $ekuoids = new-object -com \"X509Enrollment.CObjectIds.1\"\r\n    $ekuoids.add($serverauthoid)\r\n    $ekuext = new-object -com \"X509Enrollment.CX509ExtensionEnhancedKeyUsage.1\"\r\n    $ekuext.InitializeEncode($ekuoids)\r\n\r\n$cert = new-object -com \"X509Enrollment.CX509CertificateRequestCertificate.1\"\r\n    $cert.InitializeFromPrivateKey(2, $key, \"\")\r\n    $cert.Subject = $name\r\n    $cert.Issuer = $cert.Subject\r\n    $cert.NotBefore = $StartDate\r\n    $cert.NotAfter = $EndDate\r\n    $cert.X509Extensions.Add($ekuext)\r\n    $cert.Encode()\r\n\r\n$enrollment = new-object -com \"X509Enrollment.CX509Enrollment.1\"\r\n    $enrollment.InitializeFromRequest($cert)\r\n    $certdata = $enrollment.CreateRequest(0)\r\n    $enrollment.InstallResponse(2, $certdata, 0, \"\")\r\n    return $true\r\n}\r\n\r\nfunction ExportPFXFile()\r\n{\r\n    if($CommonName.ToLower().StartsWith(\"cn=\"))\r\n    {\r\n        # Remove CN from common name\r\n        $CommonName = $CommonName.Substring(3)\r\n    }\r\n    if($Password -eq $null)\r\n    {\r\n        $Password = Read-Host -Prompt \"Enter Password to protect private key\" -AsSecureString\r\n    }\r\n    $cert = Get-ChildItem -Path Cert:\\LocalMachine\\my | where-object{$_.Subject -eq \"CN=$CommonName\"}\r\n\r\n    Export-PfxCertificate -Cert $cert -Password $Password -FilePath \"$($CommonName).pfx\"\r\n    Export-Certificate -Cert $cert -Type CERT -FilePath \"$CommonName.cer\"\r\n}\r\n\r\nfunction RemoveCertsFromStore()\r\n{\r\n    # Once the certificates have been been exported we can safely remove them from the store\r\n    if($CommonName.ToLower().StartsWith(\"cn=\"))\r\n    {\r\n        # Remove CN from common name\r\n        $CommonName = $CommonName.Substring(3)\r\n    }\r\n    $certs = Get-ChildItem -Path Cert:\\LocalMachine\\my | Where-Object{$_.Subject -eq \"CN=$CommonName\"}\r\n    foreach($c in $certs)\r\n    {\r\n        remove-item $c.PSPath\r\n    }\r\n}\r\n\r\nif(CreateSelfSignedCertificate)\r\n{\r\n    ExportPFXFile\r\n    RemoveCertsFromStore\r\n}<\/pre>\n<ol start=\"2\">\n<li>Open a PowerShell console as an <em>administrator<\/em><\/li>\n<li>Create the certificate with a command like <code>.\\Create-SelfSignedCertificate.ps1 -CommonName &quot;SharePointOnlinePrivateKey&quot; -StartDate 2024-04-01 -EndDate 2035-01-01<\/code> (if you receive an error, make sure to allow running this kind of script with the command <code>Set-ExecutionPolicy RemoteSigned<\/code>)<\/li>\n<li>A password is required (e.g. &quot;HereIsMyPass1223&quot;)<\/li>\n<li>Two files are created: <code>SharePointOnlinePrivateKey.pfx<\/code> and <code>SharePointOnlinePrivateKey.cer<\/code><\/li>\n<\/ol>\n<p>We&#39;re going to install OpenSSL to convert the <code>pfx<\/code> file to a <code>pem<\/code>:<\/p>\n<ol>\n<li>Install <a href=\"https:\/\/www.openssl.org\/\">OpenSSL<\/a> (e.g. the light version for Win64) as an <em>administrator<\/em><\/li>\n<li>Find the OpenSSL installation directory (e.g. <code>C:\\Program Files\\OpenSSL-Win64\\<\/code>)<\/li>\n<li>Open the <code>start.bat<\/code> file from this OpenSSL directory<\/li>\n<li>A command window opens \u2013 go to the directory where the <code>SharePointOnlinePrivateKey.pfx<\/code> file is (e.g. <code>cd C:\\Users\\Aymeric\\Documents\\nodejs\\spo-experiments<\/code>)<\/li>\n<li>In the OpenSSL command window, type <code>openssl pkcs12 -in SharePointOnlinePrivateKey.pfx -out SharePointOnlinePrivateKey.pem<\/code> (the password entered in the previous steps will be asked three times)<\/li>\n<\/ol>\n<p>We should now have a file called <code>SharePointOnlinePrivateKey.pem<\/code><\/p>\n<h1 id=\"azure-application\">Azure Application<\/h1>\n<p>It&#39;s time to create the related Azure application:<\/p>\n<ol>\n<li>Go to <a href=\"https:\/\/portal.azure.com\">https:\/\/portal.azure.com<\/a><\/li>\n<li>Go to the Microsoft Entra ID section<\/li>\n<li>Go to the &quot;App Registrations&quot;, and click on &quot;New registration&quot;<\/li>\n<li>I won&#39;t detail all the steps to create the app<\/li>\n<li>Give a name to the app (e.g. &quot;<strong>SharePoint Online Remote Access<\/strong>&quot;)<\/li>\n<\/ol>\n<h2 id=\"get-thumbprint\">Get Thumbprint<\/h2>\n<p>We need the thumbprint:<\/p>\n<ol>\n<li>Go to &quot;<strong>Certificates &amp; secrets<\/strong>&quot; section<\/li>\n<li>Go to &quot;<strong>Certificates<\/strong>&quot; tab (<a href=\"https:\/\/github.com\/SharePoint\/sp-dev-docs\/issues\/5889#issuecomment-645225813\">see how<\/a>) and upload the <code>SharePointOnlinePrivateKey.cer<\/code> file you created before<\/li>\n<li>Once uploaded, it will provide &quot;<strong>Thumbprint<\/strong>&quot; (e.g. &quot;F7D8D4F2F140E79B215899BD93A14D0790947789&quot;) \u2013 copy this value for a later use.<\/li>\n<\/ol>\n<h2 id=\"get-client-id-and-tenant-id\">Get Client Id and Tenant Id<\/h2>\n<p>We need the <code>clientId<\/code> and the <code>tenantId<\/code>:<\/p>\n<ol>\n<li>From the overview page of your app, copy the &quot;<strong>Application (client) Id<\/strong>&quot; (e.g. &quot;75284292-7515-4e2f-aae9-d1777527dd7b&quot;) and the &quot;<strong>Directory (tenant) ID<\/strong>&quot; (e.g. &quot;945c177a-83a2-4e80-9f8c-5a91be5752dd&quot;)<\/li>\n<\/ol>\n<h2 id=\"platform-configuration\">Platform configuration<\/h2>\n<p>Additional configuration required for the application:<\/p>\n<ol>\n<li>Go to &quot;<strong>Authentication<\/strong>&quot; menu, and under &quot;<strong>Platform configurations<\/strong>&quot;, click on &quot;<strong>Add a Platform<\/strong>&quot;<\/li>\n<li>Choose &quot;<strong>Web<\/strong>&quot; and enter <code>https:\/\/localhost<\/code> for &quot;<strong>Redirect URLs<\/strong>&quot;<\/li>\n<li>Choose &quot;<strong>Access Token<\/strong>&quot; and &quot;<strong>ID Token<\/strong>&quot; in the &quot;<strong>Implicit grant and hybrid flows<\/strong>&quot; section<\/li>\n<\/ol>\n<h2 id=\"api-permissions\">API Permissions<\/h2>\n<p>We want to give the permissions to the Azure app to get access to only one (or more) specific Sites Collection, but not all of the tenant site collections. To do so, we only need the <code>Sites.Selected<\/code> permissions (<a href=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/controlling-app-access-on-specific-sharepoint-site-collections\/\">ref1<\/a> and <a href=\"https:\/\/techcommunity.microsoft.com\/t5\/microsoft-sharepoint-blog\/develop-applications-that-use-sites-selected-permissions-for-spo\/ba-p\/3790476\">ref2<\/a>).<\/p>\n<p>First, let&#39;s select it from a Microsoft Graph perspective:<\/p>\n<ol>\n<li>Go to the &quot;<strong>API Permissions<\/strong>&quot; section from the left navigation<\/li>\n<li>Click on &quot;<strong>Add a permission<\/strong>&quot;<\/li>\n<li>Select &quot;<strong>Microsoft Graph<\/strong>&quot;<\/li>\n<li>Then &quot;<strong>Application Permissions<\/strong>&quot;<\/li>\n<li>Select &quot;<strong>Sites.Selected<\/strong>&quot;<\/li>\n<\/ol>\n<p>Then, let&#39;s select it for SharePoint Online REST perspective:<\/p>\n<ol>\n<li>From the same section, select &quot;<strong>SharePoint<\/strong>&quot;<\/li>\n<li>Then &quot;<strong>Application Permissions<\/strong>&quot;<\/li>\n<li>Select &quot;<strong>Sites.Selected<\/strong>&quot;<\/li>\n<\/ol>\n<p><a href=\"https:\/\/i.sstatic.net\/19SV6jA3.png\"><img decoding=\"async\" src=\"https:\/\/i.sstatic.net\/19SV6jA3.png\" alt=\"API Permissions screen\"><\/a><br \/>\nRemark: you might need to contact the IT team to get your API Permissions approved\/granted.<\/p>\n<h2 id=\"get-client-secret\">Get Client Secret<\/h2>\n<p>We need the <code>clientSecret<\/code>:<\/p>\n<ol>\n<li>Go to the &quot;<strong>Certificates and Secrets<\/strong>&quot; section from the left navigation<\/li>\n<li>Go to &quot;<strong>Client Secret<\/strong>&quot; and click on &quot;<strong>New client secret<\/strong>&quot;<\/li>\n<li>In &quot;<strong>Description<\/strong>&quot; you can put something like &quot;SharePoint Online Remote Access&quot;, and choose 365 days for the expiration<\/li>\n<li>Once done, make sure to copy <code>Value<\/code> (e.g. &quot;rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt&quot;) that is our <code>clientSecret<\/code><\/li>\n<\/ol>\n<h2 id=\"app-permissions-on-site-collection\">App permissions on Site Collection<\/h2>\n<p>It&#39;s time to indicate that the Azure application can have access to a specific Site Collection (e.g. <a href=\"https:\/\/mycompany.sharepoint.com\/sites\/contoso\">https:\/\/mycompany.sharepoint.com\/sites\/contoso<\/a>).<\/p>\n<p>To proceed, we need the <code>siteId<\/code>:<\/p>\n<ol>\n<li>Go to the SharePoint website (e.g. <a href=\"https:\/\/mycompany.sharepoint.com\/sites\/contoso\">https:\/\/mycompany.sharepoint.com\/sites\/contoso<\/a>)<\/li>\n<li>Click right in the page to see the source (or <code>CTRL U<\/code>)<\/li>\n<li>Search for the variable <code>siteId<\/code> (e.g. &quot;7d6d9e18-e8de-4b7c-9582-3f978726f356&quot;)<\/li>\n<\/ol>\n<p>To give the permissions to the app, a special command must be entered (see <a href=\"https:\/\/www.youtube.com\/watch?v=SNIF3zCYNUk\">this video<\/a> for more information). In theory, the site collection admin can do it, but it might be restricted in your organization, and you&#39;ll need the assistance of a tenant admin.<\/p>\n<p>We need the <code>AppId<\/code> (as known as <code>clientId<\/code>) and <code>DisplayName<\/code> (as known as the Azure app name):<\/p>\n<pre class=\"brush:powershell\">\r\n$targetSiteUri = 'https:\/\/mycompany.sharepoint.com\/sites\/contoso'\r\nConnect-PnpOnline $targetSiteUri\r\nGrant-PnPAzureADAppSitePermission -AppId '75284292-7515-4e2f-aae9-d1777527dd7b' -DisplayName 'SharePoint Online Remote Access' -Site $targetSiteUri -Permissions Write\r\n<\/pre>\n<h1 id=\"interact-with-sharepoint\">Interact with SharePoint<\/h1>\n<p>Microsoft Graph only needs a <code>client_secret<\/code>, while SharePoint REST API needs a <code>client_assertion<\/code> from the Certificate Private key.<\/p>\n<p>So, let&#39;s start with Microsoft Graph to verify our Azure app has the correct access permissions.<\/p>\n<h2 id=\"microsoft-graph\">Microsoft Graph<\/h2>\n<p>Below is the script we can use:<\/p>\n<pre class=\"brush:javascript\">\r\nconst axios = require('axios');\r\n\r\n\/\/ our constants that we found previously\r\n\/\/ they should not be hard-coded in your script, but store in a safe place\r\nconst tenantId = '945c177a-83a2-4e80-9f8c-5a91be5752dd';\r\nconst clientId = '75284292-7515-4e2f-aae9-d1777527dd7b';\r\nconst clientSecret = 'rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt';\r\nconst siteId = '7d6d9e18-e8de-4b7c-9582-3f978726f356';\r\n\r\nasync function getAccessToken() {\r\n  const resource = 'https:\/\/graph.microsoft.com';\r\n\r\n  try {\r\n    const response = await axios.post(`https:\/\/login.microsoftonline.com\/${tenantId}\/oauth2\/v2.0\/token`, new URLSearchParams({\r\n      grant_type: 'client_credentials',\r\n      client_id: clientId,\r\n      client_secret: clientSecret,\r\n      scope: `${resource}\/.default`\r\n    }), {\r\n      headers: {\r\n        'Content-Type': 'application\/x-www-form-urlencoded'\r\n      }\r\n    });\r\n\r\n    return response.data.access_token;\r\n  } catch (error) {\r\n    console.error('Error getting access token:', error);\r\n    throw error;\r\n  }\r\n}\r\n\r\n\/\/ get a SharePoint item using Graph\r\ngetAccessToken()\r\n.then(token => {\r\n  \/\/ we need to find the siteId for each level\r\n  \/\/ we could use `https:\/\/graph.microsoft.com\/v1.0\/sites\/${siteId}\/sites` to find the children sites Id\r\n  \/\/ mycompany.sharepoint.com\/sites\/contoso\/Toolbox -> 919f3ff8-2cfd-469d-ac2c-cf58475ee72a\r\n  \/\/ mycompany.sharepoint.com\/sites\/contoso\/Toolbox\/Demo -> 37af7205-ebd1-49e5-a770-cdb182d2ae81\r\n  return axios.get(`https:\/\/graph.microsoft.com\/v1.0\/sites\/${siteId}\/sites\/919f3ff8-2cfd-469d-ac2c-cf58475ee72a\/sites\/37af7205-ebd1-49e5-a770-cdb182d2ae81\/lists\/Assets\/items\/123?expand=fields(select=Title)`, {\r\n    headers:{\r\n      'Authorization':'Bearer '+token,\r\n      'Accept': 'application\/json'\r\n    }\r\n  })\r\n})\r\n.then(res => {\r\n  console.log(res.data);\r\n})\r\n.catch(err => {\r\n  console.log(\"err => \", err.response.data)\r\n})\r\n<\/pre>\n<h2 id=\"sharepoint-online-rest-api\">SharePoint Online REST API<\/h2>\n<p>If it worked with the Graph version, it means we can now test with the SharePoint REST API:<\/p>\n<pre class=\"brush:javascript\">\r\nconst axios = require('axios');\r\nconst fs = require(\"fs\");\r\nconst crypto = require(\"crypto\");\r\nconst msal = require(\"@azure\/msal-node\");\r\nconst { v4: uuid } = require('uuid');\r\n\r\n\/\/ our constants that we found previously\r\n\/\/ they should not be hard-coded in your script, but store in a safe place\r\nconst tenantId = '945c177a-83a2-4e80-9f8c-5a91be5752dd';\r\nconst clientId = '75284292-7515-4e2f-aae9-d1777527dd7b';\r\nconst clientSecret = 'rVE7Q~Z1BhRXaljbj7SPg~U2HYJRR-feckrxKbCt';\r\nconst resource = 'https:\/\/mycompany.sharepoint.com';\r\nconst privateKeyPassPhrase = \"HereIsMyPass1223\";\r\nconst thumbprint = \"F7D8D4F2F140E79B215899BD93A14D0790947789\";\r\n\r\n\/\/ generate the client_assertion\r\nfunction getClientAssertion () {\r\n  \/\/ source: https:\/\/github.com\/AzureAD\/microsoft-authentication-library-for-js\/blob\/dev\/lib\/msal-node\/docs\/certificate-credentials.md\r\n  \/\/ decrypt the private key\r\n  const privateKeySource = fs.readFileSync(\".\/SharePointOnlinePrivateKey.pem\");\r\n  const privateKeyObject = crypto.createPrivateKey({\r\n      key: privateKeySource,\r\n      passphrase: privateKeyPassPhrase,\r\n      format: \"pem\",\r\n  });\r\n\r\n  const privateKey = privateKeyObject.export({\r\n      format: \"pem\",\r\n      type: \"pkcs8\",\r\n  });\r\n  \r\n  const config = {\r\n    auth: {\r\n      clientId: clientId,\r\n      authority: `https:\/\/login.microsoftonline.com\/${tenantId}`,\r\n      clientCertificate: {\r\n        thumbprint: thumbprint, \/\/ a 40-digit hexadecimal string\r\n        privateKey: privateKey,\r\n      },\r\n    },\r\n  };\r\n\r\n  \/\/ Create msal application object\r\n  const cca = new msal.ConfidentialClientApplication(config);\r\n  const helper = {\r\n    createNewGuid:uuid\r\n  }\r\n  const issuer = clientId;\r\n  const jwtAudience = `https:\/\/login.microsoftonline.com\/${tenantId}\/oauth2\/v2.0\/token`;\r\n  return cca.clientAssertion.getJwt(helper, issuer, jwtAudience);\r\n}\r\n\r\nasync function getAccessToken() {\r\n  \/\/ see https:\/\/github.com\/SharePoint\/sp-dev-docs\/issues\/5889 and https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/v2-oauth2-client-creds-grant-flow#second-case-access-token-request-with-a-certificate\r\n  try {\r\n    const response = await axios.post(`https:\/\/login.microsoftonline.com\/${tenantId}\/oauth2\/v2.0\/token`, new URLSearchParams({\r\n      client_id: clientId,\r\n      scope: `${resource}\/.default`,\r\n      client_assertion_type: \"urn:ietf:params:oauth:client-assertion-type:jwt-bearer\",\r\n      client_assertion: getClientAssertion(),\r\n      grant_type: 'client_credentials',\r\n    }), {\r\n      headers: {\r\n        'Content-Type': 'application\/x-www-form-urlencoded'\r\n      }\r\n    });\r\n\r\n    return response.data.access_token;\r\n  } catch (error) {\r\n    console.error('Error getting access token:', error);\r\n    throw error;\r\n  }\r\n}\r\n\r\n\/\/ run the script\r\ngetAccessToken()\r\n.then(token => {\r\n  return axios.get(`https:\/\/mycompany.sharepoint.com\/sites\/contoso\/Toolbox\/Demo\/_api\/web\/lists\/getbytitle('Assets')\/items(123)?$select=Title`, {\r\n    headers:{\r\n      'Authorization':'Bearer '+token,\r\n      'Accept': 'application\/json; odata=verbose'\r\n    }\r\n  })\r\n})\r\n.then(response => {\r\n  console.log(response.data)\r\n})\r\n.catch(console.log)\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>(duplicate of this StackOverflow answer) Start a new project In a new folder, type npm init to start a new project. Make sure to use a Node &gt;= v14 (I use Node v18 \u2013 and Volta can be useful to manage several versions of Node for Windows) Install some dependencies: npm install axios @azure\/msal-node uuid [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","hide_page_title":"","footnotes":""},"categories":[23,170,20,33],"tags":[],"class_list":["post-2291","post","type-post","status-publish","format-standard","hentry","category-debug","category-english","category-niveau-expert","category-programmation"],"_links":{"self":[{"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/posts\/2291","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/comments?post=2291"}],"version-history":[{"count":4,"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/posts\/2291\/revisions"}],"predecessor-version":[{"id":2295,"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/posts\/2291\/revisions\/2295"}],"wp:attachment":[{"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/media?parent=2291"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/categories?post=2291"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.kodono.info\/wordpress\/wp-json\/wp\/v2\/tags?post=2291"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}