Skip to main content

Node.js Quickstart

Use this guide to add ThunderID authentication to a vanilla Node.js application using the @thunderid/node SDK and the built-in http module. No framework required.

What You Will Learn
  • Create a Node.js project
  • Install the @thunderid/node package
  • Add working sign-in and sign-out routes
  • Protect routes and display the signed-in user's profile
Prerequisites
  • About 15 minutes
  • Node.js 18+ installed on your system
  • npm, yarn, or pnpm
  • Your preferred code editor
1

Run ThunderID

Start a local ThunderID instance. Pick the method that works best for you:

$npx thunderid

Requires Node.js 18+

Full install guide →

Once it's running, the console is available at https://localhost:8090.

2

Create an Application

Open the Console at https://localhost:8090/console, navigate to Applications, and click Add Application:

  1. Under Technology, select Node.js.
  2. Enter a name (e.g. My Node.js App) and create an application. The rest of the settings can stay at their defaults.
  3. Copy both the Client ID and Client Secret from the window that pops up. The Client ID can also be found in the General tab.
  4. Under General, add http://localhost:3000/callback to the list of Authorized Redirect URIs.
3

Create a Node.js Project

Initialize a new Node.js project:

mkdir my-node-app
cd my-node-app
npm init -y
4

Install @thunderid/node

Install the ThunderID Node.js SDK:

npm install @thunderid/node
5

Initialize the Client

Create an index.js file and initialize the ThunderIDNodeClient with your application credentials:

index.js
const http = require('http');
const { URL } = require('url');
const { randomUUID } = require('crypto');
const { ThunderIDNodeClient } = require('@thunderid/node');

const PORT = 3000;
const SESSION_COOKIE = 'tid_session';

const auth = new ThunderIDNodeClient();

function getSessionId(req) {
const cookieHeader = req.headers.cookie ?? '';
for (const part of cookieHeader.split(';')) {
const [name, value] = part.trim().split('=');
if (name === SESSION_COOKIE) return decodeURIComponent(value);
}
return null;
}

async function main() {
await auth.initialize({
clientId: '<your-client-id>',
clientSecret: '<your-client-secret>',
baseUrl: 'https://localhost:8090',
afterSignInUrl: 'http://localhost:3000/callback',
afterSignOutUrl: 'http://localhost:3000',
});

const server = http.createServer(async (req, res) => {
// routes added in the next step
});

server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
}

main();
Configuration

Replace <your-client-id> and <your-client-secret> with the values from your ThunderID application. Set the authorized redirect URL in your application settings to http://localhost:3000/callback.

Configuration Parameters

ParameterDescription
clientIdThe Client ID from your ThunderID application
clientSecretThe Client Secret from your ThunderID application
baseUrlYour ThunderID instance URL (e.g., https://localhost:8090)
afterSignInUrlThe callback URL ThunderID redirects to after sign-in
afterSignOutUrlThe URL to redirect to after sign-out
6

Add Sign-In and Sign-Out Routes

The signIn method works in two phases: it first redirects the user to ThunderID, then handles the authorization code on the callback. Session state is tied to a session ID stored in a cookie.

Replace the // routes added in the next step comment with:

index.js
    const url = new URL(req.url, `http://localhost:${PORT}`);

try {
if (url.pathname === '/login') {
let sessionId = getSessionId(req);
const extraHeaders = {};
if (!sessionId) {
sessionId = randomUUID();
extraHeaders['Set-Cookie'] =
`${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;
}
await auth.signIn((authUrl) => {
res.writeHead(302, { ...extraHeaders, Location: authUrl });
res.end();
}, sessionId);

} else if (url.pathname === '/callback') {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const sessionState = url.searchParams.get('session_state');
const sessionId = getSessionId(req);

if (!sessionId || !code || !state) {
res.writeHead(400);
return res.end('Bad request');
}

await auth.signIn(() => {}, sessionId, code, sessionState, state);
res.writeHead(302, { Location: '/profile' });
res.end();

} else if (url.pathname === '/logout') {
const sessionId = getSessionId(req);
if (!sessionId) {
res.writeHead(302, { Location: '/' });
return res.end();
}
const signOutUrl = await auth.signOut(sessionId);
res.writeHead(302, {
Location: signOutUrl,
'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
});
res.end();

}
} catch {
res.writeHead(500);
res.end('Internal server error');
}

How the sign-in flow works:

  1. GET /login: generates a session ID, stores it in a cookie, and calls signIn with an authUrlCallback. The callback receives the ThunderID authorization URL and redirects the user's browser there.
  2. GET /callback: ThunderID redirects back with code and state query parameters. Calling signIn again with those values exchanges the code for tokens and stores the session.
  3. GET /logout: calls signOut to get the OIDC end-session URL, clears the local cookie, then redirects the browser to complete the logout at ThunderID.
7

Protect a Route and Display User Info

Use isSignedIn to guard routes and getUser to retrieve the authenticated user's profile. Add these inside the same try block, before the closing }:

index.js
      if (url.pathname === '/') {
const sessionId = getSessionId(req);
const signedIn = sessionId && (await auth.isSignedIn(sessionId));
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(signedIn
? '<a href="/profile">View profile</a> | <a href="/logout">Sign out</a>'
: '<a href="/login">Sign in</a>'
);

} else if (url.pathname === '/profile') {
const sessionId = getSessionId(req);
if (!sessionId || !(await auth.isSignedIn(sessionId))) {
res.writeHead(302, { Location: '/login' });
return res.end();
}
const user = await auth.getUser(sessionId);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<h1>Welcome, ${user.name || user.username}!</h1>
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>First name:</strong> ${user.given_name}</p>
<p><strong>Last name:</strong> ${user.family_name}</p>
<a href="/logout">Sign out</a>
`);

} else if (url.pathname === '/login') {
8

Complete index.js

Here is the full file for reference:

index.js
const http = require('http');
const { URL } = require('url');
const { randomUUID } = require('crypto');
const { ThunderIDNodeClient } = require('@thunderid/node');

const PORT = 3000;
const SESSION_COOKIE = 'tid_session';

const auth = new ThunderIDNodeClient();

function getSessionId(req) {
const cookieHeader = req.headers.cookie ?? '';
for (const part of cookieHeader.split(';')) {
const [name, value] = part.trim().split('=');
if (name === SESSION_COOKIE) return decodeURIComponent(value);
}
return null;
}

async function main() {
await auth.initialize({
clientId: '<your-client-id>',
clientSecret: '<your-client-secret>',
baseUrl: 'https://localhost:8090',
afterSignInUrl: 'http://localhost:3000/callback',
afterSignOutUrl: 'http://localhost:3000',
});

const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://localhost:${PORT}`);

try {
if (url.pathname === '/') {
const sessionId = getSessionId(req);
const signedIn = sessionId && (await auth.isSignedIn(sessionId));
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(signedIn
? '<a href="/profile">View profile</a> | <a href="/logout">Sign out</a>'
: '<a href="/login">Sign in</a>'
);

} else if (url.pathname === '/profile') {
const sessionId = getSessionId(req);
if (!sessionId || !(await auth.isSignedIn(sessionId))) {
res.writeHead(302, { Location: '/login' });
return res.end();
}
const user = await auth.getUser(sessionId);
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<h1>Welcome, ${user.name || user.username}!</h1>
<p><strong>Email:</strong> ${user.email}</p>
<p><strong>First name:</strong> ${user.given_name}</p>
<p><strong>Last name:</strong> ${user.family_name}</p>
<a href="/logout">Sign out</a>
`);

} else if (url.pathname === '/login') {
let sessionId = getSessionId(req);
const extraHeaders = {};
if (!sessionId) {
sessionId = randomUUID();
extraHeaders['Set-Cookie'] =
`${SESSION_COOKIE}=${sessionId}; HttpOnly; SameSite=Lax; Path=/`;
}
await auth.signIn((authUrl) => {
res.writeHead(302, { ...extraHeaders, Location: authUrl });
res.end();
}, sessionId);

} else if (url.pathname === '/callback') {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const sessionState = url.searchParams.get('session_state');
const sessionId = getSessionId(req);

if (!sessionId || !code || !state) {
res.writeHead(400);
return res.end('Bad request');
}

await auth.signIn(() => {}, sessionId, code, sessionState, state);
res.writeHead(302, { Location: '/profile' });
res.end();

} else if (url.pathname === '/logout') {
const sessionId = getSessionId(req);
if (!sessionId) {
res.writeHead(302, { Location: '/' });
return res.end();
}
const signOutUrl = await auth.signOut(sessionId);
res.writeHead(302, {
Location: signOutUrl,
'Set-Cookie': `${SESSION_COOKIE}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`,
});
res.end();

} else {
res.writeHead(404);
res.end('Not found');
}
} catch {
res.writeHead(500);
res.end('Internal server error');
}
});

server.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
}

main();
9

Run Your App

Start the server:

node index.js

Open http://localhost:3000.

Test credentials

You'll need a user to sign in with. If you haven't created one yet, open https://localhost:8090/console, navigate to Users, and add a test user with an email and password.

Success

You should see the sign-in link. Click it to be redirected to the ThunderID-hosted sign-in page. After authenticating with your test user, you'll return to the /profile route with your user profile displayed.

What's Next

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© WSO2 LLC. All rights reserved.Privacy PolicyCookie Policy