Skip to content

Auth.js

Vercel Connect is available on all plans and is subject to the Vercel Connect terms

Auth.js can use Vercel Connect as an OAuth provider for signing users into your Next.js application. The @vercel/connect/authjs entrypoint creates the provider configuration, handles Vercel authentication, and maps the Connect user profile into an Auth.js user.

This tutorial uses TypeScript and the Next.js App Router.

Before you start, you need:

  • A Next.js App Router project linked to Vercel.
  • A Vercel Connect connector that supports user authorization.
  • A project link between the connector and your Vercel project for each environment that will serve the Auth.js callback.
  • The connector UID, such as oauth/linear, or its opaque scl_... identifier.
  • An upstream OAuth application, if you use a custom OAuth connector. Register https://connect.vercel.com/callback as the redirect URI at the provider. Managed connectors handle this provider callback for you.

Follow the Vercel Connect quickstart to create a connector and link it to your project.

This integration signs a user into your application. It does not return an access token for calling the provider's API. See Choose the correct integration for the distinction.

  1. Install Auth.js, its core types, and Vercel Connect:

    Terminal
    pnpm i next-auth@beta @auth/core @vercel/connect
    Terminal
    yarn add next-auth@beta @auth/core @vercel/connect
    Terminal
    npm i next-auth@beta @auth/core @vercel/connect
    Terminal
    bun add next-auth@beta @auth/core @vercel/connect

    The Auth.js adapter requires @auth/core version 0.37.0 or later.

  2. Link your local directory and pull a development OpenID Connect (OIDC) token:

    Terminal
    vercel link
    vercel env pull

    The command writes VERCEL_OIDC_TOKEN to .env.local. Vercel provides this token automatically after deployment. The Connect adapter fetches an OIDC token for every token exchange, so you do not configure a static OAuth client secret.

    Add your connector UID and an Auth.js secret to .env.local:

    .env.local
    CONNECTOR_LINEAR=oauth/linear
    AUTH_SECRET=replace-with-a-random-secret

    Generate a long, random value for AUTH_SECRET, and do not commit .env.local.

  3. Create an Auth.js configuration and add the result of connect() to AuthConfig.providers:

    auth.config.ts
    import type { AuthConfig } from '@auth/core';
    import { connect } from '@vercel/connect/authjs';
     
    export const authConfig: AuthConfig = {
      providers: [
        connect({
          id: 'linear',
          name: 'Linear',
          connector: process.env.CONNECTOR_LINEAR!,
        }),
      ],
    };

    The options control these values:

    OptionPurpose
    idAuth.js provider ID. It also becomes the final callback URL path segment.
    nameProvider name shown in your sign-in interface.
    connectorConnector UID or opaque scl_... identifier.
    scopesOptional scopes that replace the default scope list.

    The example uses linear as the provider ID. Auth.js therefore handles these callback URLs:

    • Local: http://localhost:3000/api/auth/callback/linear
    • Production: https://your-domain.example/api/auth/callback/linear

    The route must be available on every origin where users sign in. Keep the provider ID consistent when you call signIn(). Do not register these Auth.js URLs with the upstream provider. The upstream provider redirects to Vercel Connect, and Vercel Connect redirects to your Auth.js callback.

  4. Create the root Auth.js module:

    auth.ts
    import NextAuth from 'next-auth';
    import { authConfig } from './auth.config';
     
    export const { auth, handlers, signIn, signOut } = NextAuth(authConfig);

    This module exposes the route handlers, session helper, and server actions used by your application.

  5. Create the Auth.js catch-all route and export both handlers:

    app/api/auth/[...nextauth]/route.ts
    import { handlers } from '@/auth';
     
    export const { GET, POST } = handlers;

    Auth.js now owns the sign-in, callback, sign-out, and session endpoints under /api/auth.

  6. Use the provider ID from connect() when you start the sign-in flow:

    app/page.tsx
    import { auth, signIn, signOut } from '@/auth';
     
    export default async function Home() {
      const session = await auth();
     
      if (!session?.user) {
        return (
          <form
            action={async () => {
              'use server';
              await signIn('linear');
            }}
          >
            <button type="submit">Sign in with Linear</button>
          </form>
        );
      }
     
      return (
        <main>
          <p>Signed in as {session.user.email ?? session.user.name}</p>
          <form
            action={async () => {
              'use server';
              await signOut();
            }}
          >
            <button type="submit">Sign out</button>
          </form>
        </main>
      );
    }

    Run the application and select Sign in with Linear:

    Terminal
    pnpm dev

    After the provider and Vercel Connect consent flows finish, Auth.js redirects the browser to /api/auth/callback/linear and creates the session.

The provider created by connect() uses the OAuth 2.0 authorization code flow with Proof Key for Code Exchange (PKCE). Auth.js creates the code verifier and challenge, keeps the verifier in its secure cookie, and sends the verifier when it exchanges the authorization code. You do not need to create PKCE parameters or change the provider's checks setting.

The adapter also:

  • Configures the OAuth client without a static client secret.
  • Fetches a Vercel OIDC token for each request to the Connect token endpoint.
  • Sends the connector identifier and Vercel OIDC token through HTTP Basic authentication for that token exchange.
  • Adds offline_access once, even if your custom scope list already contains it, so Vercel Connect can mint refresh tokens.

Without a scopes option, the adapter requests openid, profile, and email. It then adds offline_access.

Passing scopes replaces the three defaults. Include every OpenID Connect and provider scope your application needs:

auth.config.ts
import type { AuthConfig } from '@auth/core';
import { connect } from '@vercel/connect/authjs';
 
export const authConfig: AuthConfig = {
  providers: [
    connect({
      id: 'linear',
      name: 'Linear',
      connector: process.env.CONNECTOR_LINEAR!,
      scopes: ['openid', 'profile', 'email', 'read'],
    }),
  ],
};

This configuration requests openid profile email read offline_access. Confirm the provider supports each custom scope before adding it.

The Connect userinfo endpoint returns a ConnectProfile:

Connect fieldAuth.js user fieldBehavior
subidRequired stable user identifier.
namenameOptional display name.
emailemailUses null when Connect returns no email.
pictureimageOptional profile image.
email_verifiedNot mappedAvailable on ConnectProfile for custom use.

To change the standard mapping, spread the generated provider and override its profile function:

auth.config.ts
import type { AuthConfig } from '@auth/core';
import {
  connect,
  type ConnectProfile,
} from '@vercel/connect/authjs';
 
const linear = connect({
  id: 'linear',
  name: 'Linear',
  connector: process.env.CONNECTOR_LINEAR!,
});
 
export const authConfig: AuthConfig = {
  providers: [
    {
      ...linear,
      profile(profile: ConnectProfile) {
        return {
          id: profile.sub,
          name: profile.name ?? profile.email ?? profile.sub,
          email: profile.email ?? null,
          image: profile.picture,
        };
      },
    },
  ],
};

Treat sub as opaque. Do not derive authorization decisions from its format.

Authorization runs in your project's context. During the token exchange, Vercel Connect authenticates the project with its OIDC token and applies the connector's project links. The connector and project must belong to the correct team, and the link must include the current environment.

For local development, refresh the team and project context by running vercel link and vercel env pull again if the OIDC token expires or points to the wrong project.

Auth.js, provider API token access, and Vercel Passport solve different tasks:

TaskUseResult
Sign users into your Next.js application through a Connect connectorconnect() from @vercel/connect/authjsAn Auth.js user and application session
Call a provider API as an app or usergetToken() from @vercel/connectA short-lived provider access token
Require identity before a visitor can reach a deploymentVercel PassportDeployment protection enforced before your application route runs

Do not use the Auth.js session token as a provider API token. If your signed-in application also needs to call the provider, request a narrowly scoped token with getToken() and an appropriate subject. See Tokens for token subjects and provider scopes.

Passport does not create an Auth.js session inside your application. It protects the deployment at the Vercel layer. Use Auth.js when your application needs its own sign-in flow, session callbacks, and user records.

Last updated August 27, 2026

Was this helpful?