OIDC Node.js Example
This guide demonstrates how to integrate NetActuate OIDC authentication into a Node.js application using the openid-client library with TypeScript.
Prerequisites
- Node.js 18 or later
- A NetActuate OIDC client ID and client secret (configured at Account → Settings → OIDC)
Project Setup
Initialize a new project and install dependencies:
mkdir netactuate-oidc-node && cd netactuate-oidc-node
npm init -y
npm install openid-client express express-session
npm install -D typescript @types/express @types/express-session ts-node
Create a tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist"
}
}
Configuration
Create a config.ts file:
export const oidcConfig = {
issuerUrl: "https://portal.netactuate.com",
clientId: process.env.OIDC_CLIENT_ID || "your-client-id",
clientSecret: process.env.OIDC_CLIENT_SECRET || "your-client-secret",
redirectUri: "http://localhost:3000/callback",
scope: "openid profile email",
};
Note: Always use environment variables for secrets in production. Never commit credentials to version control.
Full Implementation
Create an app.ts file:
import express from "express";
import session from "express-session";
import { Issuer, Client, generators } from "openid-client";
import { oidcConfig } from "./config";
const app = express();
// Configure session middleware
app.use(
session({
secret: "replace-with-secure-random-string",
resave: false,
saveUninitialized: false,
})
);
let oidcClient: Client;
async function initializeOIDC(): Promise<void> {
const issuer = await Issuer.discover(oidcConfig.issuerUrl);
console.log("Discovered issuer:", issuer.metadata.issuer);
oidcClient = new issuer.Client({
client_id: oidcConfig.clientId,
client_secret: oidcConfig.clientSecret,
redirect_uris: [oidcConfig.redirectUri],
response_types: ["code"],
});
}
// Home page
app.get("/", (req, res) => {
res.send('<html><body><a href="/login">Log in with NetActuate</a></body></html>');
});
// Initiate OIDC login
app.get("/login", (req, res) => {
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
// Store code verifier in session for PKCE
(req.session as any).codeVerifier = codeVerifier;
const authUrl = oidcClient.authorizationUrl({
scope: oidcConfig.scope,
code_challenge: codeChallenge,
code_challenge_method: "S256",
state: generators.state(),
});
res.redirect(authUrl);
});
// Handle callback
app.get("/callback", async (req, res) => {
try {
const params = oidcClient.callbackParams(req);
const codeVerifier = (req.session as any).codeVerifier;
const tokenSet = await oidcClient.callback(oidcConfig.redirectUri, params, {
code_verifier: codeVerifier,
});
// Get user info
const userInfo = await oidcClient.userinfo(tokenSet.access_token!);
res.json({
message: "Authentication successful",
user: {
sub: userInfo.sub,
email: userInfo.email,
name: userInfo.name,
},
tokens: {
accessTokenExpires: tokenSet.expires_at,
hasRefreshToken: !!tokenSet.refresh_token,
},
});
} catch (err) {
console.error("Callback error:", err);
res.status(500).json({ error: "Authentication failed" });
}
});
// Token refresh endpoint
app.get("/refresh", async (req, res) => {
try {
const refreshToken = (req.session as any).refreshToken;
if (!refreshToken) {
res.status(401).json({ error: "No refresh token available" });
return;
}
const tokenSet = await oidcClient.refresh(refreshToken);
(req.session as any).refreshToken = tokenSet.refresh_token;
res.json({
message: "Token refreshed",
accessTokenExpires: tokenSet.expires_at,
});
} catch (err) {
console.error("Refresh error:", err);
res.status(500).json({ error: "Token refresh failed" });
}
});
// Start server
const PORT = 3000;
initializeOIDC().then(() => {
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
});
Running the Application
Set your credentials as environment variables and start the server:
export OIDC_CLIENT_ID="your-client-id"
export OIDC_CLIENT_SECRET="your-client-secret"
npx ts-node app.ts
Open your browser and navigate to http://localhost:3000. Click the login link to initiate the OIDC flow.
Security Features
This example includes several security best practices:
- PKCE (Proof Key for Code Exchange): Protects the authorization code flow against interception attacks
- Server-side sessions: Tokens are stored server-side, not in the browser
- Token refresh: Demonstrates how to refresh expired access tokens
Production Considerations
- Use a production-grade session store (e.g., Redis)
- Enable HTTPS for all endpoints
- Implement CSRF protection
- Add proper logging and error handling
- Set secure cookie options (
secure: true,httpOnly: true,sameSite: 'strict')
Need Help?
If you run into issues, contact NetActuate Support.