# AuthKit

## Introduction

Integrating AuthKit into your app can be done in less than ten minutes. In this guide, we'll walk you through adding a hosted authentication flow to your application using AuthKit.

In addition to this guide, there are a variety of [example apps](https://workos.com/docs/authkit/example-apps) available to help with your integration.

## Before getting started

To get the most out of this guide, you'll need:

- A [WorkOS account](https://dashboard.workos.com/)
- Your WorkOS [API Key](https://workos.com/docs/glossary/api-key) and [Client ID](https://workos.com/docs/glossary/client-id)

***

## (1) Configure your project

Let's add the necessary dependencies and configuration in your WorkOS Dashboard.

### Install dependencies

First, install the required Node SDK via `npm`.

```bash title="Install Node SDK"
npm install @workos-inc/node
```

For CSRF protection on state-changing routes like logout, also install `csrf-csrf`:

```bash title="Install CSRF protection"
npm install csrf-csrf cookie-parser
```

### Configure a redirect URI

A redirect URI is a callback endpoint that WorkOS will redirect to after a user has authenticated. This endpoint will exchange the authorization code returned by WorkOS for an authenticated [User object](https://workos.com/docs/reference/authkit/user). We'll create this endpoint in the next step.

You can set a redirect URI in the **Redirects** section of the [WorkOS Dashboard](https://dashboard.workos.com). We recommend using `http://localhost:3000/callback` as the default here.

WorkOS supports using wildcard characters in Redirect URIs, but not for the default Redirect URI. More information about wildcard characters support can be found in the [Redirect URIs](https://workos.com/docs/sso/redirect-uris/wildcard-characters) guide.

![Dashboard redirect URI](https://images.workoscdn.com/images/a7525cf3-ae4e-4dcd-91dd-27965b005472.png?auto=format\&fit=clip\&q=80)

When users sign out of their application, they will be redirected to your app's [Sign-out redirect](https://workos.com/docs/authkit/sessions/configuring-sessions/sign-out-redirect) location which is configured in the same dashboard area.

### Configure sign-in endpoint

Sign-in requests should originate from your application. In some instances, requests may not begin at your app. For example, some users might bookmark the hosted sign-in page or they might be led directly to the hosted sign-in page when clicking on a password reset link in an email.

In these cases, AuthKit will detect when a sign-in request did not originate at your application and redirect to your application's sign-in endpoint. This is an endpoint that you define at your application that redirects users to sign in using AuthKit. We'll create this endpoint in the next step.

You can configure the sign-in endpoint from the **Redirects** section of the WorkOS dashboard.

![Sign-in endpoint](https://images.workoscdn.com/images/25b53ea7-95ba-48cc-b6e7-ccd1b1bc35eb.png?auto=format\&fit=clip\&q=80)

### Set secrets

To make calls to WorkOS, provide the API key and the client ID. Store these values as managed secrets and pass them to the SDKs either as environment variables or directly in your app's configuration depending on your preferences.

```plain title="Environment variables"
WORKOS_API_KEY='sk_example_123456789'
WORKOS_CLIENT_ID='client_123456789'
```

> The code examples use your staging API keys when [signed in](https://dashboard.workos.com)

***

## (2) Add AuthKit to your app

Let's integrate the hosted authentication flow into your app.

### Set up the frontend

To demonstrate AuthKit, we only need a simple page with links to logging in and out.

#### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <title>AuthKit example</title>
  </head>
  <body>
    <h1>AuthKit example</h1>
    <p>This is an example of how to use AuthKit with an HTML frontend.</p>
    <p>
      <a href="/login">Sign in</a>
    </p>
    <form action="/logout" method="POST" id="logout-form">
      <input type="hidden" name="_csrf" id="csrf-token" />
      <button type="submit">Sign out</button>
    </form>

    <script>
      // Fetch CSRF token on page load
      fetch('/csrf-token')
        .then((r) => r.json())
        .then((data) => {
          document.getElementById('csrf-token').value = data.csrfToken;
        });
    </script>
  </body>
</html>
```

Clicking the "Sign in" and "Sign out" links should invoke actions on our server, which we'll set up next.

### Add a sign-in endpoint

We'll need a sign-in endpoint to direct users to sign in (or sign up) using AuthKit before redirecting them back to your application. This endpoint should generate an AuthKit authorization URL server side and redirect the user to it.

You can use the optional state parameter to encode arbitrary information to help restore application `state` between redirects.

For this guide we'll be using the `express` web server for Node. This guide won't cover how to set up an Express app, but you can find more information in the [Express documentation](https://expressjs.com/en/starter/installing.html).

#### server.js

```jsx
require('dotenv').config();

const path = require('path');
const express = require('express');
const { WorkOS } = require('@workos-inc/node');

const app = express();
const workos = new WorkOS(process.env.WORKOS_API_KEY, {
  clientId: process.env.WORKOS_CLIENT_ID,
});

// This `/login` endpoint should be registered as the login endpoint
// on the "Redirects" page of the WorkOS Dashboard.
app.get('/login', (req, res) => {
  const authorizationUrl = workos.userManagement.getAuthorizationUrl({
    // Specify that we'd like AuthKit to handle the authentication flow
    provider: 'authkit',

    // The callback endpoint that WorkOS will redirect to after a user authenticates
    redirectUri: 'http://localhost:3000/callback',
    clientId: process.env.WORKOS_CLIENT_ID,
  });

  // Redirect the user to the AuthKit sign-in page
  res.redirect(authorizationUrl);
});

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'index.html'));
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
```

> WorkOS will redirect to your [Redirect URI](https://workos.com/docs/glossary/redirect-uri) if there is an issue generating an authorization URL. Read our [API Reference](https://workos.com/docs/reference) for more details.

### Add a callback endpoint

Next, let's add the callback endpoint (referenced in [Configure a redirect URI](https://workos.com/docs/authkit/1-configure-your-project/configure-a-redirect-uri)) which will exchange the authorization code (valid for 10 minutes) for an authenticated User object.

#### server.js

```js
const express = require('express');
const { WorkOS } = require('@workos-inc/node');

const app = express();

const workos = new WorkOS(process.env.WORKOS_API_KEY, {
  clientId: process.env.WORKOS_CLIENT_ID,
});

// +diff-start
app.get('/callback', async (req, res) => {
  // The authorization code returned by AuthKit
  const code = req.query.code;

  if (!code) {
    return res.status(400).send('No code provided');
  }

  const { user } = await workos.userManagement.authenticateWithCode({
    code,
    clientId: process.env.WORKOS_CLIENT_ID,
  });

  // Use the information in `user` for further business logic.

  // Redirect the user to the homepage
  return res.redirect('/');
});
// +diff-end
```

## (3) Handle the user session

Session management helper methods are included in our SDKs to make integration easy. For security reasons, sessions are automatically "sealed", meaning they are encrypted with a strong password.

### Create a session password

The SDK requires you to set a strong password to encrypt cookies. This password must be 32 characters long. You can generate a secure password by using the [1Password generator](https://1password.com/password-generator/) or the `openssl` library via the command line:

```bash title="Generate a strong password"
openssl rand -base64 32
```

Then add it to the environment variables file.

```plain title=".env"
WORKOS_API_KEY='sk_example_123456789'
WORKOS_CLIENT_ID='client_123456789'

# +diff-start
WORKOS_COOKIE_PASSWORD='<your password>'
# +diff-end
```

### Save the encrypted session

Next, use the SDK to authenticate the user and return a password protected session. The refresh token is considered sensitive as it can be used to re-authenticate, hence why the session is encrypted before storing it in a session cookie.

#### server.js

```js
const cookieParser = require('cookie-parser');

app.use(cookieParser());

app.get('/callback', async (req, res) => {
  // The authorization code returned by AuthKit
  const code = req.query.code;

  if (!code) {
    return res.status(400).send('No code provided');
  }

  // +diff-start
  try {
    const authenticateResponse =
      await workos.userManagement.authenticateWithCode({
        clientId: process.env.WORKOS_CLIENT_ID,
        code,
        session: {
          sealSession: true,
          cookiePassword: process.env.WORKOS_COOKIE_PASSWORD,
        },
      });

    const { user, sealedSession } = authenticateResponse;

    // Store the session in a cookie
    res.cookie('wos-session', sealedSession, {
      path: '/',
      httpOnly: true,
      secure: true,
      sameSite: 'lax',
    });

    // Use the information in `user` for further business logic.

    // Redirect the user to the homepage
    return res.redirect('/');
  } catch (error) {
    return res.redirect('/login');
  }
  // +diff-end
});
```

We should also present some of the user information on our frontend. Let's update the default route to read the session cookie and display user information:

#### server.js

```js
const fs = require('fs');

app.get('/', async (req, res) => {
  let user = null;

  try {
    const session = workos.userManagement.loadSealedSession({
      sessionData: req.cookies['wos-session'],
      cookiePassword: process.env.WORKOS_COOKIE_PASSWORD,
    });

    const authResult = await session.authenticate();
    if (authResult.authenticated) {
      user = authResult.user;
    }
  } catch (e) {
    // Not authenticated, user stays null
  }

  let html = fs.readFileSync(path.join(__dirname, 'index.html'), 'utf8');

  if (user) {
    html = html.replace('{{USER_DATA}}', `<p>Welcome, ${user.email}!</p>`);
  } else {
    html = html.replace('{{USER_DATA}}', '');
  }

  res.send(html);
});
```

And, we should make sure to update the index page to present this info.

#### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <title>AuthKit example</title>
  </head>
  <body>
    <h1>AuthKit example</h1>
    <!--+diff-start -->
    {{USER_DATA}}
    <!-- +diff-end -->
    <p>This is an example of how to use AuthKit with an HTML frontend.</p>
    <p>
      <a href="/login">Sign in</a>
    </p>
    <form action="/logout" method="POST" id="logout-form">
      <input type="hidden" name="_csrf" id="csrf-token" />
      <button type="submit">Sign out</button>
    </form>

    <script>
      // Fetch CSRF token on page load
      fetch('/csrf-token')
        .then((r) => r.json())
        .then((data) => {
          document.getElementById('csrf-token').value = data.csrfToken;
        });
    </script>
  </body>
</html>
```

### Protected routes

Then, use middleware to specify which routes should be protected. If the session has expired, use the SDK to attempt to generate a new one.

#### server.js

```js
import { WorkOS } from '@workos-inc/node';

const workos = new WorkOS(process.env.WORKOS_API_KEY, {
  clientId: process.env.WORKOS_CLIENT_ID,
});

// Auth middleware function
async function withAuth(req, res, next) {
  const session = workos.userManagement.loadSealedSession({
    sessionData: req.cookies['wos-session'],
    cookiePassword: process.env.WORKOS_COOKIE_PASSWORD,
  });

  const { authenticated, reason } = await session.authenticate();

  if (authenticated) {
    return next();
  }

  // If the cookie is missing, redirect to login
  if (!authenticated && reason === 'no_session_cookie_provided') {
    return res.redirect('/login');
  }

  // If the session is invalid, attempt to refresh
  try {
    const { authenticated, sealedSession } = await session.refresh();

    if (!authenticated) {
      return res.redirect('/login');
    }

    // update the cookie
    res.cookie('wos-session', sealedSession, {
      path: '/',
      httpOnly: true,
      secure: true,
      sameSite: 'lax',
    });

    // Redirect to the same route to ensure the updated cookie is used
    return res.redirect(req.originalUrl);
  } catch (e) {
    // Failed to refresh access token, redirect user to login page
    // after deleting the cookie
    res.clearCookie('wos-session');
    res.redirect('/login');
  }
}
```

Add the middleware to the route that should only be accessible to logged in users.

#### server.js

```js
// Specify the `withAuth` middleware function we defined earlier to protect this route
app.get('/dashboard', withAuth, async (req, res) => {
  const session = workos.userManagement.loadSealedSession({
    sessionData: req.cookies['wos-session'],
    cookiePassword: process.env.WORKOS_COOKIE_PASSWORD,
  });

  const { user } = await session.authenticate();

  console.log(`User ${user.firstName} is logged in`);

  // ... render dashboard page
});
```

### Ending the session

Finally, ensure the user can end their session by redirecting them to the logout URL. After successfully signing out, the user will be redirected to your app's [Sign-out redirect](https://workos.com/docs/authkit/sessions/configuring-sessions/sign-out-redirect) location, which is configured in the WorkOS dashboard.

#### server.js

```js
const { doubleCsrf } = require('csrf-csrf');
const cookieParser = require('cookie-parser');

app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));

const { generateCsrfToken, doubleCsrfProtection } = doubleCsrf({
  getSecret: () => process.env.CSRF_SECRET,
  getSessionIdentifier: (req) => req.cookies['wos-session'] ?? '',
});

// Endpoint to get CSRF token for forms
app.get('/csrf-token', (req, res) => {
  const csrfToken = generateCsrfToken(req, res);
  res.json({ csrfToken });
});

app.post('/logout', doubleCsrfProtection, async (req, res) => {
  const session = workos.userManagement.loadSealedSession({
    sessionData: req.cookies['wos-session'],
    cookiePassword: process.env.WORKOS_COOKIE_PASSWORD,
  });

  const url = await session.getLogoutUrl();

  res.clearCookie('wos-session');
  res.redirect(url);
});
```

> **Note:** <strong>CSRF Protection:</strong> The logout endpoint uses POST to prevent
> unintended logouts from browser prefetching. CSRF protection with
> <code>csrf-csrf</code> prevents cross-site request forgery attacks. The
> frontend fetches a CSRF token from <code>/csrf-token</code> and includes it
> in the logout form submission.

> If you haven't configured a [Sign-out redirect](https://workos.com/docs/authkit/sessions/configuring-sessions/sign-out-redirect) in the WorkOS dashboard, users will see an error when logging out.

## Validate the authentication flow

To test all of this out, start your server with `node server.js`, navigate to `localhost:3000`, and sign up for an account.

You can then sign in with the newly created credentials and see the user listed in the **Users** section of the [WorkOS Dashboard](https://dashboard.workos.com).

![Dashboard showing newly created user](https://images.workoscdn.com/images/54fa6e6c-4c6f-4959-9301-344aeb4eeac8.png?auto=format\&fit=clip\&q=80)
