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)