Steven's Knowledge

Getting Started

Integrate Auth0 with a Node app - signup, login, sessions, protected routes, JWT validation

Getting Started

This page walks through integrating Auth0 with a Node.js Express app — login, sessions, JWT validation on an API. The patterns transfer to Clerk, WorkOS, Keycloak, Cognito, etc. — only the SDK names change.

Sign Up for Auth0

  1. Sign up at auth0.com.
  2. Create a Tenant (your isolated identity environment).
  3. Create an Application — "Regular Web Application" for an SSR Node app, "Single Page Application" for a React/Vue SPA.
  4. Note the Domain, Client ID, Client Secret.

In Settings for your application:

  • Allowed Callback URLs: http://localhost:3000/callback
  • Allowed Logout URLs: http://localhost:3000
  • Allowed Web Origins: http://localhost:3000

Server-Side App (SSR / Express)

npm install express-openid-connect dotenv
// app.js
require('dotenv').config();
const express = require('express');
const { auth, requiresAuth } = require('express-openid-connect');

const app = express();

app.use(auth({
  authRequired: false,
  auth0Logout: true,
  issuerBaseURL: `https://${process.env.AUTH0_DOMAIN}`,
  baseURL: 'http://localhost:3000',
  clientID: process.env.AUTH0_CLIENT_ID,
  clientSecret: process.env.AUTH0_CLIENT_SECRET,
  secret: process.env.SESSION_SECRET,   // for encrypting session cookie
}));

// Public route
app.get('/', (req, res) => {
  res.send(req.oidc.isAuthenticated()
    ? `Hi, ${req.oidc.user.name}! <a href="/logout">Logout</a>`
    : `<a href="/login">Login</a>`);
});

// Protected route
app.get('/profile', requiresAuth(), (req, res) => {
  res.json(req.oidc.user);
});

app.listen(3000);

That's it. auth() handles /login, /logout, /callback; requiresAuth() is the middleware that gates routes. Visit /, click Login → universal login screen → consent → back to your app, logged in.

The session is cookie-based, signed/encrypted with SESSION_SECRET. Auth0 also exposes the user info via req.oidc.user — name, email, picture, etc.

SPA + API (the React + API Pattern)

The modern pattern: frontend gets a JWT, sends it to your API, API validates the JWT.

Frontend (React with @auth0/auth0-react)

import { Auth0Provider, useAuth0 } from '@auth0/auth0-react';

function App() {
  return (
    <Auth0Provider
      domain={import.meta.env.VITE_AUTH0_DOMAIN}
      clientId={import.meta.env.VITE_AUTH0_CLIENT_ID}
      authorizationParams={{
        redirect_uri: window.location.origin,
        audience: 'https://api.example.com',
        scope: 'openid profile email read:orders',
      }}
    >
      <Main />
    </Auth0Provider>
  );
}

function Main() {
  const { loginWithRedirect, logout, user, isAuthenticated, getAccessTokenSilently } = useAuth0();

  async function callApi() {
    const token = await getAccessTokenSilently();
    const res = await fetch('http://localhost:3001/orders', {
      headers: { Authorization: `Bearer ${token}` },
    });
    console.log(await res.json());
  }

  if (!isAuthenticated) return <button onClick={loginWithRedirect}>Login</button>;
  return (
    <>
      <p>Hi {user.name}</p>
      <button onClick={callApi}>Call API</button>
      <button onClick={() => logout()}>Logout</button>
    </>
  );
}

getAccessTokenSilently fetches a JWT scoped to your API. The SDK handles caching, refresh, and the PKCE flow.

API (Express + JWT validation)

npm install express-jwt jwks-rsa
const express = require('express');
const { expressjwt: jwt } = require('express-jwt');
const jwksRsa = require('jwks-rsa');

const app = express();

const checkJwt = jwt({
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`,
  }),
  audience: 'https://api.example.com',
  issuer: `https://${process.env.AUTH0_DOMAIN}/`,
  algorithms: ['RS256'],
});

app.use(checkJwt);

app.get('/orders', (req, res) => {
  res.json({
    user_id: req.auth.sub,
    scope: req.auth.scope,
    orders: ['ord-1', 'ord-2'],
  });
});

app.listen(3001);

checkJwt middleware:

  1. Reads the Authorization: Bearer ... header.
  2. Fetches Auth0's public signing keys (cached) from JWKS.
  3. Validates the JWT signature, aud, iss, exp.
  4. Attaches the decoded claims to req.auth.

The API is now stateless — no session DB lookup needed for auth. The JWT carries everything.

Common Tokens

After login, Auth0 (or any IdP) returns:

TokenPurposeLifetime
ID tokenProves who the user is (signed JWT with sub, email, name)~10 min
Access tokenSent to your API as Bearer ...~10-60 min
Refresh tokenUsed to get new access tokens without re-loginDays to ~indefinite

Where they live:

  • Server-rendered app → all three in an HttpOnly cookie session
  • SPA → in memory (refresh token via httpOnly cookie or rotating refresh in storage)
  • Mobile native → secure storage (Keychain / Keystore)

Never put a long-lived access token or refresh token in localStorage of an SPA. XSS = total account takeover.

Adding Social Login

In the Auth0 dashboard, Authentication → Social — toggle on Google, GitHub, Apple, etc. Configure their OAuth credentials.

That's it. Your existing login page now has "Sign in with Google" buttons. You wrote zero code.

This is the value of an IdP: the login screen is a product the IdP maintains; you get features as they ship them.

Roles and Permissions

For role-based access:

  1. Auth0 dashboard → Roles → create "admin", "user", "viewer".
  2. Assign permissions to each role.
  3. Assign roles to users (or auto-assign via rules / actions on signup).
  4. Enable Add Permissions in the Access Token for your API.

Now the JWT has a permissions claim:

{
  "sub": "auth0|abc123",
  "permissions": ["read:orders", "write:orders"],
  ...
}
function requireScope(scope) {
  return (req, res, next) => {
    if (!req.auth.permissions?.includes(scope)) {
      return res.status(403).json({ error: 'insufficient scope' });
    }
    next();
  };
}

app.post('/orders', checkJwt, requireScope('write:orders'), createOrder);

For more complex RBAC patterns, see the next page.

What's Next

You can log users in and validate JWTs on an API. Next:

  • Patterns — SSO/SAML, SCIM, RBAC, multi-tenant, passkeys, refresh-token rotation
  • Best Practices — token security, session strategy, migration, lock-in, observability

On this page