8 October 2026 ยท Dean Hume
A Modern Guide to Using OAuth 2.0 with Node.js

Ever followed an OAuth tutorial, copied the code, and then found out the library it uses was deprecated years ago? Yeah, me too. It's frustrating - especially when all you wanted was an access token.
A little while ago I wrote A Modern Guide to Using OAuth 2.0 with C# and Visual Studio Code, and it got a lot more attention than I expected. A few people asked if I could do the same thing for Node.js, so here it is.
This guide walks you through implementing OAuth 2.0 in Node.js using modern libraries and built-in features. No extra HTTP client, no config library - just a handful of lines of code and a working token at the end of it.
Whether you're building a script, a back-end service, or an MCP server that needs to talk to a protected API (more on that in a future post), this is a clean place to start.
On this page
- Prerequisites
- Step 1: Create a New Node.js Project
- Step 2: Install Required Packages
- Step 3: Configure Your OAuth Settings
- Step 4: Authenticate and Acquire a Token
- Step 5: Make an Authenticated API Call
- Optional: Set up an Entra ID test OAuth App
- Conclusion
Prerequisites
Before we dive in, make sure you have the following:
- Node.js 22 or later (anything from 20.6 will work for this guide, but I'd go with the current LTS)
- Visual Studio Code
- A basic understanding of HTTP and REST APIs
- A registered OAuth 2.0 application (e.g. via Google, Microsoft, or a custom provider)
Let's get started!
Step 1: Create a New Node.js Project
Open your terminal and run:
mkdir oauth-node-demo
cd oauth-node-demo
npm init -y
npm pkg set type=module
That last line switches the project to ES modules, which means we can use import and top-level await without any extra setup.
Step 2: Install Required Packages
Since I've already got an Entra app setup, we'll use @azure/msal-node to handle the OAuth flow for this example:
npm install @azure/msal-node
That's the only dependency. Node.js 18 and above ships with fetch built in, so we don't need node-fetch or anything else to make API calls. Since Node 20.6 can load .env files natively, we don't need dotenv either.
Step 3: Configure Your OAuth Settings
Create a .env file in the root of your project to store your credentials:
CLIENT_ID=your-client-id
TENANT_ID=your-tenant-id
CLIENT_SECRET=your-client-secret
SCOPES=https://graph.microsoft.com/.default
๐ What is tenant-id?
This is the unique identifier (GUID) for your Microsoft Entra ID tenant. You can find it in the Azure portal under Microsoft Entra ID > Overview. Alternatively, you can use your domain name (e.g. contoso.onmicrosoft.com) in place of the GUID. This might be different depending on your OAuth provider.
โ ๏ธ Keep your secrets out of source control.
Add.envto your.gitignorebefore you do anything else. I've seen more than one client secret accidentally pushed to a public repo, and it's not a fun clean-up job.
echo ".env" >> .gitignore
Step 4: Authenticate and Acquire a Token
Create a file called index.js and add the following. It uses the client credentials flow, which is the right choice when your app is calling an API as itself rather than on behalf of a signed-in user:
import { ConfidentialClientApplication } from "@azure/msal-node";
const { CLIENT_ID, TENANT_ID, CLIENT_SECRET, SCOPES } = process.env;
const msalClient = new ConfidentialClientApplication({
auth: {
clientId: CLIENT_ID,
authority: `https://login.microsoftonline.com/${TENANT_ID}`,
clientSecret: CLIENT_SECRET,
},
});
const result = await msalClient.acquireTokenByClientCredential({
scopes: SCOPES.split(","),
});
console.log(`Access Token: ${result.accessToken}`);
Run it with the --env-file flag so Node loads your .env file:
node --env-file=.env index.js
If everything is set up correctly, you'll see a long access token printed to the console. Congratulations - you just authenticated with OAuth 2.0!
A quick note on tokens: MSAL caches them in memory for you, so calling acquireTokenByClientCredential again will return the cached token until it's close to expiring. You don't need to write your own refresh logic for this flow.
Step 5: Make an Authenticated API Call
Now let's use that token to call a protected resource. Add this to the bottom of index.js:
const response = await fetch("https://graph.microsoft.com/v1.0/users", {
headers: {
Authorization: `Bearer ${result.accessToken}`,
},
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log(JSON.stringify(data, null, 2));
โ
Testing endpoint: https://graph.microsoft.com/v1.0/users
This endpoint returns a list of users in your Entra ID tenant. To use it, make sure your app registration has the User.Read.All application permission and that you've granted admin consent.
Run it again:
node --env-file=.env index.js
If all works as expected, you should see a response similar to this:
{
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
"value": [
{
"businessPhones": [],
"displayName": "Dean Hume",
"givenName": "Dean",
"jobTitle": null,
"mail": null,
"mobilePhone": null,
"officeLocation": null,
"preferredLanguage": "en",
"surname": "Hume",
"userPrincipalName": "email.com#EXT#@email.onmicrosoft.com",
"id": "id-response-goes-here"
}
]
}
If you get a 403 instead, it's almost always one of two things - the permission hasn't been added to your app registration, or admin consent hasn't been granted. I've lost more time to that second one than I'd like to admit.
Optional: Set up an Entra ID test OAuth App
This step is optional, as you might want to use your own OAuth provider. However, I wanted to be sure that my code actually worked, so I set up an app in Microsoft Entra ID to test with.
If you'd like to do the same, these resources should help:
Conclusion
OAuth 2.0 doesn't have to be intimidating. With a modern library like MSAL and the features that now ship with Node.js, you can securely authenticate and call a protected API in well under 30 lines of code - and with only one dependency.
Have you run into any OAuth headaches when working with Node.js? I'd love to hear about them in the comments or over on GitHub - and if there's a flow you'd like me to cover next, let me know.


