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)