> ## Documentation Index
> Fetch the complete documentation index at: https://auth0-docs-event-stream-action-templates.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> This guide demonstrates how to integrate Auth0, add authentication, and display user profile information in an Express.js web application using the express-openid-connect SDK.

# Add Login to Your Express Application

export const HowToSchema = () => <script type="application/ld+json">
    {'{"@context":"https://schema.org","@type":"HowTo"}'}
  </script>;

export const CreateInteractiveApp = ({placeholderText = 'Auth0', appType = 'regular_web', allowedCallbackUrls = ['localhost:3000'], allowedLogoutUrls = ['localhost:3000'], allowedOriginUrls = ['localhost:3000']}) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [storeReady, setStoreReady] = useState(false);
  const [displayForm, setDisplayForm] = useState(true);
  useEffect(() => {
    const init = () => setStoreReady(true);
    if (window.rootStore) {
      window.rootStore.clientStore.setSelectedClient(null);
      window.rootStore.clientStore.setSelectedClientSecret(undefined);
      init();
    } else {
      window.addEventListener('adu:storeReady', init);
    }
    return () => {
      window.removeEventListener('adu:storeReady', init);
    };
  }, []);
  useEffect(() => {
    if (!storeReady) return;
    const disposer = autorun(() => {
      const rootStore = window.rootStore;
      setIsAuthenticated(rootStore.sessionStore.isAuthenticated);
    });
    return () => {
      disposer();
    };
  }, [storeReady]);
  if (!storeReady || typeof window === 'undefined' || !displayForm) {
    return <></>;
  }
  const login = () => {
    const baseUrl = window.rootStore.config.apiBaseUrl;
    const returnTo = encodeURIComponent(window.location.href);
    window.location.href = `${baseUrl}/auth/user/login?returnTo=${returnTo}`;
  };
  const Card = ({className = '', children}) => {
    return <div className={`
          flex border rounded-2xl
          border-gray-950/10 dark:border-white/10
          py-3.5 px-4 gap-2
          text-sm text-gray-900 dark:text-gray-200
          ${className}
        `}>
        {children}
      </div>;
  };
  const Button = ({children, ...props}) => {
    return <button className="bg-[--button-primary] text-[--foreground-inverse] px-[1.125rem] py-1.5 rounded-lg font-medium" {...props}>
        {children}
      </button>;
  };
  const CreateApplicationForm = () => {
    const [name, setName] = useState('');
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState('');
    const handleSubmit = async () => {
      if (!name.trim()) {
        setError('Application name is required');
        return;
      }
      setIsLoading(true);
      setError(null);
      try {
        await window.rootStore.clientStore.createClient({
          name: name.trim(),
          app_type: appType,
          callbacks: allowedCallbackUrls,
          allowed_logout_urls: allowedLogoutUrls,
          web_origins: allowedOriginUrls,
          client_metadata: {
            created_by: 'quickstart-docs-app-creation-component'
          }
        });
        setDisplayForm(false);
      } catch (err) {
        console.error('Error creating client:', err);
        const errorMessage = err instanceof Error ? err.message : 'Failed to create application';
        setError(errorMessage);
      } finally {
        setIsLoading(false);
      }
    };
    return <Card className="flex-col items-start p-4 gap-3.75">
        <span className="font-medium text-gray-900 dark:text-gray-200">
          Create Auth0 App
        </span>
        <div className="w-full flex gap-2">
          <input id="app-name" name={name} className="
              w-full max-w-[448px] h-11 py-2 px-4 
              border rounded-lg border-gray-950/10 dark:border-white/10 
              text-gray-900 dark:text-gray-200
              focus:outline-none dark:focus:outline-none
            " placeholder={`My ${placeholderText} App`} value={name} onChange={e => setName(e.target.value)} />
          <Button onClick={handleSubmit}>
            {isLoading ? 'Creating...' : 'Create'}
          </Button>
        </div>
        {error && <p className="text-red-500">{error}</p>}
      </Card>;
  };
  const SignInForm = () => {
    return <Card className="items-center">
        <Button onClick={login}>Log in</Button> <span>to create the app</span>
      </Card>;
  };
  return isAuthenticated ? <CreateApplicationForm /> : <SignInForm />;
};

<HowToSchema />

<Callout icon="pencil" color="#FFC107" iconType="solid">
  A new **Beta** version of this quickstart is available using the `@auth0/auth0-express` SDK, which will soon replace this guide. [Try the Beta quickstart →](/docs/quickstart/webapp/express-beta)
</Callout>

<Accordion title="Use AI to integrate Auth0" icon="microchip-ai" iconType="solid" defaultOpen>
  If you use an AI coding assistant like Claude Code, Cursor, or GitHub Copilot, you can add Auth0 authentication automatically in minutes using [agent skills](https://agentskills.io/home).

  **Install:**

  ```bash theme={null}
  npx skills add https://github.com/auth0/agent-skills --skill auth0-express
  ```

  **Then ask your AI assistant:**

  ```text theme={null}
  Add Auth0 authentication to my Express app
  ```

  Your AI assistant will automatically create your Auth0 application, fetch credentials, install `express-openid-connect`, configure the middleware, and set up your routes. [Full agent skills documentation →](/docs/quickstart/agent-skills)
</Accordion>

<Note>
  **Prerequisites:** Before you begin, ensure you have the following installed:

  * [Node.js](https://nodejs.org/) 18 LTS or newer
  * [npm](https://www.npmjs.com/) 10+ or [yarn](https://yarnpkg.com/) 1.22+
  * [jq](https://jqlang.github.io/jq/) - Required for Auth0 CLI setup (optional)

  **Express Version Compatibility:** This quickstart works with Express 4.17.0 and newer.
</Note>

## Get Started

This guide demonstrates how to integrate Auth0, add authentication, and display user profile information in an Express.js web application using the `express-openid-connect` SDK.

***

## 1. Create a new project

Create a new directory for your Express application and initialize a Node.js project.

```bash theme={null}
mkdir auth0-express && cd auth0-express
```

```bash theme={null}
npm init -y
```

Create the project structure:

```bash theme={null}
touch index.js .env
```

***

## 2. Install the Auth0 Express SDK

Install `express-openid-connect` along with Express and dotenv for environment variable management.

```bash theme={null}
npm install express express-openid-connect dotenv
```

For development, install nodemon to automatically restart your server on file changes:

```bash theme={null}
npm install --save-dev nodemon
```

Update your `package.json` to add start scripts:

📁 **package.json**

```json theme={null}
{
  "name": "auth0-express",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "dependencies": {
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "express-openid-connect": "^2.17.1"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}
```

***

## 3. Setup your Auth0 App

Next, you need to create a new application on your Auth0 tenant and add the environment variables to your project.

You can choose to set up your Auth0 app automatically by running a CLI command, or do it manually via the Dashboard:

<Tabs>
  <Tab title="CLI">
    Run the following shell command in your project's root directory to create an Auth0 application and generate your `.env` file:

    <CodeGroup>
      ```shellscript Mac theme={null}
      # Install Auth0 CLI (if not already installed)
      brew tap auth0/auth0-cli && brew install auth0

      # Set up Auth0 app and generate .env file
      auth0 qs setup --app --type regular --framework express --port 3000 --name "My Express App"
      ```

      ```powershell Windows theme={null}
      # Install Auth0 CLI (if not already installed)
      scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
      scoop install auth0

      # Set up Auth0 app and generate .env file
      auth0 qs setup --app --type regular --framework express --port 3000 --name "My Express App"
      ```
    </CodeGroup>

    <Note>
      This command will:

      1. Check if you're authenticated (and prompt for login if needed)
      2. Create an Auth0 Regular Web Application configured for `http://localhost:3000`
      3. Generate a `.env` file with `ISSUER_BASE_URL`, `CLIENT_ID`, `SECRET`, and `BASE_URL`
    </Note>
  </Tab>

  <Tab title="Dashboard">
    1. Go to the [Auth0 Dashboard](https://manage.auth0.com/dashboard/)
    2. Navigate to **Applications** → **Create Application**
    3. Enter a name for your application (e.g., "My Express App")
    4. Select **Regular Web Applications** and click **Create**
    5. In the **Settings** tab, configure the following:

    | Setting               | Value                   |
    | --------------------- | ----------------------- |
    | Allowed Callback URLs | `http://localhost:3000` |
    | Allowed Logout URLs   | `http://localhost:3000` |

    6. Scroll down and click **Save Changes**
    7. Copy the **Domain** and **Client ID** values from the **Basic Information** section

    Create your `.env` file with the following values:

    📁 **.env**

    ```bash theme={null}
    ISSUER_BASE_URL=https://YOUR_AUTH0_DOMAIN
    CLIENT_ID=YOUR_CLIENT_ID
    SECRET=use-a-long-random-string-at-least-32-characters
    BASE_URL=http://localhost:3000
    ```

    <Warning>
      Replace `YOUR_AUTH0_DOMAIN` with your Auth0 tenant domain (e.g., `dev-abc123.us.auth0.com`) and `YOUR_CLIENT_ID` with your application's Client ID from the dashboard.
    </Warning>

    Generate a secure secret for session encryption:

    ```bash theme={null}
    openssl rand -hex 32
    ```

    Copy the output and use it as the `SECRET` value in your `.env` file.
  </Tab>
</Tabs>

***

## 4. Configure the middleware

Add the Auth0 middleware to your Express application. The `auth()` middleware handles session management and automatically creates `/login`, `/logout`, and `/callback` routes.

📁 **index.js**

```javascript theme={null}
require('dotenv').config();
const express = require('express');
const { auth } = require('express-openid-connect');

const app = express();
const port = process.env.PORT || 3000;

// Auth0 configuration
const config = {
  authRequired: false,      // Allow public routes
  auth0Logout: true,        // Use Auth0 logout endpoint
  secret: process.env.SECRET,
  baseURL: process.env.BASE_URL,
  clientID: process.env.CLIENT_ID,
  issuerBaseURL: process.env.ISSUER_BASE_URL,
};

// Apply the auth middleware
app.use(auth(config));

// Home route - public
app.get('/', (req, res) => {
  res.send(req.oidc.isAuthenticated() ? 'Logged in' : 'Logged out');
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
```

**What this does:**

* `authRequired: false` allows both authenticated and unauthenticated users to access routes by default
* `auth0Logout: true` ensures users are logged out from Auth0 as well as your app
* The middleware automatically provides routes at `/login`, `/logout`, and `/callback`
* User session is stored in an encrypted cookie

***

## 5. Create login, logout, and profile routes

Now add routes to display login/logout links and a protected profile page.

📁 **index.js**

```javascript theme={null}
require('dotenv').config();
const express = require('express');
const { auth, requiresAuth } = require('express-openid-connect');

const app = express();
const port = process.env.PORT || 3000;

// Auth0 configuration
const config = {
  authRequired: false,
  auth0Logout: true,
  secret: process.env.SECRET,
  baseURL: process.env.BASE_URL,
  clientID: process.env.CLIENT_ID,
  issuerBaseURL: process.env.ISSUER_BASE_URL,
};

// Apply the auth middleware
app.use(auth(config));

// Home route - shows login/logout status
app.get('/', (req, res) => {
  const isAuthenticated = req.oidc.isAuthenticated();

  res.send(`
    <html>
      <head>
        <title>Auth0 Express Quickstart</title>
        <style>
          body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 2rem; max-width: 600px; margin: 0 auto; }
          a { color: #0066cc; text-decoration: none; margin-right: 1rem; }
          a:hover { text-decoration: underline; }
          .status { padding: 1rem; border-radius: 4px; margin: 1rem 0; }
          .logged-in { background: #d4edda; color: #155724; }
          .logged-out { background: #f8d7da; color: #721c24; }
        </style>
      </head>
      <body>
        <h1>Auth0 Express Quickstart</h1>
        <div class="status ${isAuthenticated ? 'logged-in' : 'logged-out'}">
          ${isAuthenticated ? '✓ You are logged in' : '✗ You are logged out'}
        </div>
        <nav>
          ${isAuthenticated
            ? '<a href="/profile">Profile</a> | <a href="/logout">Logout</a>'
            : '<a href="/login">Login</a>'}
        </nav>
      </body>
    </html>
  `);
});

// Protected profile route - requires authentication
app.get('/profile', requiresAuth(), (req, res) => {
  const user = req.oidc.user;

  res.send(`
    <html>
      <head>
        <title>Profile - Auth0 Express</title>
        <style>
          body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 2rem; max-width: 600px; margin: 0 auto; }
          a { color: #0066cc; text-decoration: none; }
          img { border-radius: 50%; }
          pre { background: #f4f4f4; padding: 1rem; border-radius: 4px; overflow-x: auto; }
          .card { border: 1px solid #ddd; border-radius: 8px; padding: 1.5rem; margin: 1rem 0; }
        </style>
      </head>
      <body>
        <h1>User Profile</h1>
        <div class="card">
          ${user.picture ? `<img src="${user.picture}" alt="Profile" width="80" />` : ''}
          <h2>${user.name || user.nickname || 'User'}</h2>
          <p><strong>Email:</strong> ${user.email || 'N/A'}</p>
        </div>
        <h3>Full User Object</h3>
        <pre>${JSON.stringify(user, null, 2)}</pre>
        <nav>
          <a href="/">← Back to Home</a> | <a href="/logout">Logout</a>
        </nav>
      </body>
    </html>
  `);
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
```

**Key points:**

* `requiresAuth()` middleware protects the `/profile` route - unauthenticated users are redirected to login
* `req.oidc.user` contains the authenticated user's profile information
* `req.oidc.isAuthenticated()` returns a boolean indicating login status
* Login and logout routes (`/login`, `/logout`) are automatically created by the `auth()` middleware

***

## 6. Run your app

Start the development server:

```bash theme={null}
npm run dev
```

Open your browser to [http://localhost:3000](http://localhost:3000).

<Check>
  **Checkpoint**

  You should now have a fully functional Auth0 login page. When you:

  1. Click "Login" - you're redirected to Auth0's Universal Login page
  2. Complete authentication - you're redirected back to your app
  3. Visit "/profile" - you see your user information
  4. Click "Logout" - you're logged out of both your app and Auth0
</Check>

***

## Advanced Usage

<AccordionGroup>
  <Accordion title="Protecting Specific Routes with requiresAuth()">
    Use the `requiresAuth()` middleware to protect individual routes that require authentication:

    ```javascript theme={null}
    const { auth, requiresAuth } = require('express-openid-connect');

    app.use(auth({ authRequired: false }));

    // Public route
    app.get('/', (req, res) => {
      res.send('Welcome! This is public.');
    });

    // Protected routes
    app.get('/dashboard', requiresAuth(), (req, res) => {
      res.send(`Hello ${req.oidc.user.name}, welcome to your dashboard!`);
    });

    app.get('/settings', requiresAuth(), (req, res) => {
      res.send('Settings page - only for authenticated users');
    });
    ```

    You can also protect all routes under a specific path using Express Router:

    ```javascript theme={null}
    const protectedRouter = express.Router();

    // All routes in this router require authentication
    protectedRouter.use(requiresAuth());

    protectedRouter.get('/dashboard', (req, res) => {
      res.send('Protected dashboard');
    });

    protectedRouter.get('/settings', (req, res) => {
      res.send('Protected settings');
    });

    app.use('/app', protectedRouter);
    // Routes: /app/dashboard, /app/settings are all protected
    ```
  </Accordion>

  <Accordion title="Calling Protected APIs with Access Tokens">
    To call external APIs that require an access token, configure the SDK to request one:

    📁 **index.js** (updated configuration)

    ```javascript theme={null}
    const config = {
      authRequired: false,
      auth0Logout: true,
      secret: process.env.SECRET,
      baseURL: process.env.BASE_URL,
      clientID: process.env.CLIENT_ID,
      issuerBaseURL: process.env.ISSUER_BASE_URL,
      clientSecret: process.env.CLIENT_SECRET,  // Required for code flow
      authorizationParams: {
        response_type: 'code',
        audience: process.env.API_AUDIENCE,     // Your API identifier
        scope: 'openid profile email read:data',
      },
    };
    ```

    Add these to your `.env` file:

    ```bash theme={null}
    CLIENT_SECRET=your_client_secret_from_dashboard
    API_AUDIENCE=https://your-api.example.com
    ```

    Then use the access token to call your API:

    ```javascript theme={null}
    app.get('/api-data', requiresAuth(), async (req, res) => {
      try {
        let { token_type, access_token, isExpired, refresh } = req.oidc.accessToken;

        // Refresh the token if expired
        if (isExpired()) {
          const refreshed = await refresh();
          access_token = refreshed.access_token;
        }

        // Call your protected API
        const response = await fetch('https://your-api.example.com/data', {
          headers: {
            Authorization: `${token_type} ${access_token}`,
          },
        });

        const data = await response.json();
        res.json(data);
      } catch (error) {
        console.error('API call failed:', error);
        res.status(500).json({ error: 'Failed to fetch data' });
      }
    });
    ```

    <Note>
      To get refresh tokens, add `offline_access` to your scope:

      ```javascript theme={null}
      scope: 'openid profile email offline_access read:data',
      ```
    </Note>
  </Accordion>

  <Accordion title="Using Claim-Based Authorization">
    Protect routes based on user claims (roles, permissions, etc.):

    ```javascript theme={null}
    const { auth, requiresAuth, claimEquals, claimIncludes, claimCheck } = require('express-openid-connect');

    app.use(auth({ authRequired: false }));

    // Only users with role = 'admin'
    app.get('/admin', claimEquals('role', 'admin'), (req, res) => {
      res.send('Admin dashboard');
    });

    // Users whose roles array includes 'editor'
    app.get('/editor', claimIncludes('roles', 'editor'), (req, res) => {
      res.send('Editor dashboard');
    });

    // Custom claim check with logic
    app.get('/premium', claimCheck((req, claims) => {
      return claims.subscription === 'premium' || claims.role === 'admin';
    }), (req, res) => {
      res.send('Premium content');
    });
    ```

    <Note>
      Claims like `role` must be added to your tokens via Auth0 Rules or Actions. [Learn more about adding custom claims](https://auth0.com/docs/customize/actions/flows-and-triggers/login-flow/add-user-roles-to-id-and-access-tokens).
    </Note>
  </Accordion>

  <Accordion title="Custom Session Store (Redis)">
    For production environments or when running multiple server instances, use a custom session store:

    ```bash theme={null}
    npm install redis connect-redis
    ```

    ```javascript theme={null}
    const { auth } = require('express-openid-connect');
    const { createClient } = require('redis');
    const RedisStore = require('connect-redis').default;

    // Create Redis client
    const redisClient = createClient({
      url: process.env.REDIS_URL || 'redis://localhost:6379',
    });
    redisClient.connect().catch(console.error);

    const config = {
      authRequired: false,
      auth0Logout: true,
      secret: process.env.SECRET,
      baseURL: process.env.BASE_URL,
      clientID: process.env.CLIENT_ID,
      issuerBaseURL: process.env.ISSUER_BASE_URL,
      session: {
        store: new RedisStore({ client: redisClient }),
      },
    };

    app.use(auth(config));
    ```

    **When to use a custom session store:**

    * Running multiple server instances (load balancing)
    * Session data exceeds cookie size limits (\~4KB)
    * Need session persistence across server restarts
    * Using back-channel logout
  </Accordion>

  <Accordion title="Error Handling">
    Add proper error handling for authentication errors:

    ```javascript theme={null}
    const { auth } = require('express-openid-connect');

    app.use(auth({
      authRequired: false,
      auth0Logout: true,
      secret: process.env.SECRET,
      baseURL: process.env.BASE_URL,
      clientID: process.env.CLIENT_ID,
      issuerBaseURL: process.env.ISSUER_BASE_URL,
      errorOnRequiredAuth: true,  // Return 401 instead of redirecting for API routes
    }));

    // Custom error handler
    app.use((err, req, res, next) => {
      // Handle authentication errors
      if (err.statusCode === 401) {
        // For API requests, return JSON
        if (req.accepts('json')) {
          return res.status(401).json({
            error: 'Authentication required',
            login_url: '/login',
          });
        }
        // For browser requests, redirect to login
        return res.redirect('/login');
      }

      // Log the error (don't expose details to client)
      console.error('Application error:', err.message);

      res.status(err.statusCode || 500).json({
        error: 'An unexpected error occurred',
      });
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Common Issues and Solutions">
    ### "Invalid state" error after login

    **Problem:** State mismatch between the authentication request and callback.

    **Solutions:**

    1. Ensure you're using HTTPS in production
    2. Check that cookies are being set correctly (not blocked by browser)
    3. Verify callback URL matches exactly in Auth0 Dashboard

    ### "req.oidc is undefined"

    **Problem:** The `auth()` middleware is not applied before accessing `req.oidc`.

    **Solution:** Ensure `app.use(auth(config))` is called before any route that accesses `req.oidc`:

    ```javascript theme={null}
    // ✅ Correct order
    app.use(auth(config));
    app.get('/profile', requiresAuth(), (req, res) => { ... });

    // ❌ Wrong order
    app.get('/profile', requiresAuth(), (req, res) => { ... });
    app.use(auth(config));
    ```

    ### Session too large / Cookie errors

    **Problem:** User session data exceeds cookie size limits.

    **Solution:** Use a custom session store like Redis:

    ```javascript theme={null}
    session: {
      store: new RedisStore({ client: redisClient }),
    }
    ```

    ### Callback URL mismatch

    **Problem:** "Callback URL mismatch" error from Auth0.

    **Solution:**

    1. Go to your Auth0 Dashboard → Applications → Your App → Settings
    2. Add `http://localhost:3000` (or your production URL) to **Allowed Callback URLs**
    3. The URL must match **exactly** (including trailing slashes)

    ### Environment variables not loading

    **Problem:** Configuration values are `undefined`.

    **Solution:**

    1. Ensure `require('dotenv').config()` is at the top of your entry file
    2. Verify `.env` file is in the root directory
    3. Check for typos in variable names

    ```javascript theme={null}
    // Debug: Log config values (remove in production!)
    console.log('Config check:', {
      hasSecret: !!process.env.SECRET,
      hasClientID: !!process.env.CLIENT_ID,
      issuerBaseURL: process.env.ISSUER_BASE_URL,
    });
    ```
  </Accordion>
</AccordionGroup>

***

## Next Steps

Now that you have authentication working, consider exploring:

* **[Add Authorization](https://auth0.com/docs/manage-users/access-control/rbac)** - Implement role-based access control
* **[Call Protected APIs](https://auth0.com/docs/secure/tokens/access-tokens/get-access-tokens)** - Use access tokens to call your backend APIs
* **[Customize Universal Login](https://auth0.com/docs/customize/universal-login-pages)** - Brand your login experience
* **[Add Social Connections](https://auth0.com/docs/connections/social)** - Enable Google, GitHub, and other social logins
* **[Implement MFA](https://auth0.com/docs/secure/multi-factor-authentication)** - Add multi-factor authentication

***

## Resources

* **[express-openid-connect GitHub](https://github.com/auth0/express-openid-connect)** - Source code and examples
* **[API Documentation](https://auth0.github.io/express-openid-connect/)** - Complete API reference
* **[Auth0 Express Sample App](https://github.com/auth0-samples/auth0-express-webapp-sample)** - Full sample application
* **[Auth0 Community](https://community.auth0.com/)** - Get help from the community
