DEV Community

Yevheniia
Yevheniia

Posted on

๐Ÿ” Secure Your Express.js API with Okta in Minutes

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;
  },
};

Enter fullscreen mode Exit fullscreen mode

๐ŸŒ 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;
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

๐Ÿ” 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-----`
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

๐Ÿงฑ 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;
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

๐Ÿงช 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;
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“š 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)