In this guide, weβll integrate Okta authentication into an Express.js app using OAuth 2.0 and OIDC standards. Weβll verify JWTs, fetch public keys dynamically, and use robust middleware to protect our API β all in a modular, scalable way. If youβve followed my Angular + Okta article, this backend piece completes the picture.
π¦ Prerequisites
Okta Developer account with an app set up (here you can find the guide)
Node.js + Express.js basic app
npm i jsonwebtoken
npm i axios
π§© Okta Config
Create okta.config.ts to centralize your Okta metadata:
export const OktaConfig = {
issuer: "https://dev-your_acc_id.okta.com/oauth2/default",
audience: "your_audience",
get jwksUri() {
return ${this.issuer}/v1/keys;
},
};
π HTTP Request Service
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { CRUDHttpMethods } from "../enums/crud.enum";
import { logger } from "../../utils/logger";
export class HttpRequestService {
static async executeHttp(
method: CRUDHttpMethods,
url: string,
params: Record<string, unknown> = {},
headers: Record<string, any> = {},
): Promise<any> {
const config: AxiosRequestConfig = {
url,
method,
responseType: "json",
timeout: 240000,
headers,
...(method === CRUDHttpMethods.GET ? { params } : { data: params })
};
try {
const response: AxiosResponse = await axios(config);
logger("info", `${method} request to ${url} succeeded`, "HttpRequestService:executeHttp");
return response.data;
} catch (err) {
logger("error", `Failed ${method} to ${url}: ${err?.message}`, "HttpRequestService:executeHttp");
throw err;
}
}
}
π JWT Validation Service
Hereβs the token validator that fetches public keys from Okta and verifies tokens securely.
import jwt from "jsonwebtoken";
import { HttpRequestService } from "./http-request.service";
import { CRUDHttpMethods } from "../enums/crud.enum";
import { OktaConfig } from "../config/okta";
const { issuer, audience, jwksUri } = OktaConfig;
export class JwtService {
static async validateToken(token: string): Promise<any> {
const tokenParts = token.split(".");
const header = JSON.parse(atob(tokenParts[0]));
const kid = header.kid;
const key = await JwtService.getSigningKey(kid);
try {
return jwt.verify(token, key, {
audience: audience,
issuer: issuer,
algorithms: ["RS256"],
});
} catch (error) {
throw new Error(error?.message || 'Okta token verification error');
}
}
private static async getSigningKey(kid: string): Promise<string> {
try {
const data = await HttpRequestService.executeHttp(
CRUDHttpMethods.GET,
jwksUri
);
if (!data.keys || data.keys.length === 0) {
throw new Error("No Okta signing keys found in JWKS.");
}
const key = data.keys.find((k) => k.kid === kid);
if (!key) {
throw new Error(
"No matching Okta signing key found based on provided token",
);
}
const signingKey = JwtService.generatePemKey(key.n, key.e);
return signingKey;
} catch (error) {
const errorMessage = `An error has occured during retrieving signing key from Okta API. Message: ${error?.response?.data?.errorSummary || error?.message || "seems like Okta API is unavailable."}`;
throw new Error(errorMessage);
}
}
private static generatePemKey(n: string, e: string): string {
const modulus = Buffer.from(n, "base64").toString("hex");
const exponent = Buffer.from(e, "base64").toString("hex");
return (
`-----BEGIN RSA PUBLIC KEY-----\n` +
Buffer.from(`3082010a0282010100${modulus}0203${exponent}`, "hex")
.toString("base64")
.match(/.{1,64}/g)
.join("\n") +
`\n-----END RSA PUBLIC KEY-----`
);
}
}
π§± Authentication Middleware
import { Request, Response, NextFunction } from "express";
import { JwtService } from "../services/jwt-validation.service";
import { HttpStatus } from "../enums/http-status.enum";
import { httpResponseFailed } from "../helpers/api-response.helper";
export class AuthMiddleware {
static async validateToken(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
const authHeader =
req.headers["authorization"] || req.headers["Authorization"];
if (!authHeader) {
return res
.status(HttpStatus.UNAUTHIRIZED)
.json(httpResponseFailed("Missing token"));
}
try {
const token = (authHeader as string).replace(/bearer\s?/i, "").trim();
const payload = await JwtService.validateToken(token);
(req as any).user = payload;
next();
} catch (err) {
res
.status(HttpStatus.UNAUTHIRIZED)
.json(httpResponseFailed(err?.message || "Invalid token"));
return;
}
}
}
π§ͺ Wire It Up in app.ts
Attach the middleware globally or per route:
import express from "express";
import cors from "cors";
import apiRoutes from "./api/routes/index";
import { AuthMiddleware } from "./api/middlewares/auth.middleware";
import corsConfig from "./config/cors";
import { logger } from "./utils/logger";
const app = express();
app.use(cors(corsConfig));
app.use(AuthMiddleware.validateToken); // π Protect all routes
//app.use('api/private', AuthMiddleware.validateToken, privateRoutes); - in case you want to secure only certain routes
app.listen(process.env.PORT || 3000, () => {
logger("info", `Server started on port ${process.env.PORT}`, "app.ts");
});
app.use("/api", apiRoutes);
export default app;
π Resources used
π Okta Developer Documentation (OAuth 2.0 + OIDC)
π§° jsonwebtoken GitHub Repo
π Node.js Official Docs
π Express.js Middleware Concepts
π§ͺ Testing APIs with Postman
Got questions? Drop them in the comments!
Stay secure and clean. πβ¨
Top comments (0)