Aller au contenu
Documentation · Next.js

Démarrage rapide : Next.js

Ajoutez la connexion à une app Next.js avec l’App Router. Le SDK @usher/nextjs arrive bientôt ; d’ici là, ce guide utilise le flux standard d’OpenID Connect, c’est-à-dire exactement ce que le SDK fera pour vous.

Avant de commencer

  • Une app Next.js 15 ou 16 qui utilise l’App Router.
  • Un environnement Usher. Dans Usher Console, ouvrez votre projet, choisissez un environnement et notez son émetteur.

Enregistrer votre app

Dans la Console, allez dans Applications et créez une app web. Ajoutez exactement cette URI de redirection :

http://localhost:3000/auth/callback

Copiez l’ID client et le secret client. Le secret n’est affiché qu’une seule fois.

Ajouter vos variables d’environnement

.env.local
USHER_ISSUER=https://acme.usherid.com
USHER_CLIENT_ID=your_client_id
USHER_CLIENT_SECRET=your_client_secret
APP_URL=http://localhost:3000

Installer un client OpenID Connect

N’importe quel client certifié fonctionne. Ce guide utilise openid-client, qui gère la découverte, PKCE et la validation des jetons.

Terminal
pnpm add openid-client

Découvrir votre émetteur

La découverte lit une fois pour toutes les points de terminaison et les clés de l’émetteur : rien n’est donc codé en dur.

lib/usher.ts
import * as client from 'openid-client'

let config: Promise<client.Configuration> | undefined

export function usher() {
  config ??= client.discovery(
    new URL(process.env.USHER_ISSUER!),
    process.env.USHER_CLIENT_ID!,
    process.env.USHER_CLIENT_SECRET!,
  )
  return config
}

export const redirectUri = `${process.env.APP_URL}/auth/callback`

Envoyer les utilisateurs vers le Lobby

Un gestionnaire de route crée un vérificateur PKCE et une valeur state, les conserve dans un cookie de courte durée, puis redirige vers le Lobby.

app/auth/sign-in/route.ts
import * as client from 'openid-client'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { redirectUri, usher } from '@/lib/usher'

export async function GET() {
  const config = await usher()
  const codeVerifier = client.randomPKCECodeVerifier()
  const state = client.randomState()

  const jar = await cookies()
  jar.set('usher_flow', JSON.stringify({ codeVerifier, state }), {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    path: '/auth',
    maxAge: 600,
  })

  const url = client.buildAuthorizationUrl(config, {
    redirect_uri: redirectUri,
    scope: 'openid profile email offline_access',
    code_challenge: await client.calculatePKCECodeChallenge(codeVerifier),
    code_challenge_method: 'S256',
    state,
  })
  redirect(url.href)
}

Traiter le retour

Usher renvoie la personne avec un code. Échangez-le contre des jetons ; openid-client vérifie pour vous le state, le vérificateur PKCE et la signature du jeton d’ID.

app/auth/callback/route.ts
import * as client from 'openid-client'
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
import { usher } from '@/lib/usher'

export async function GET(request: Request) {
  const config = await usher()
  const jar = await cookies()
  const flow = JSON.parse(jar.get('usher_flow')?.value ?? '{}')
  jar.delete('usher_flow')

  const tokens = await client.authorizationCodeGrant(config, new URL(request.url), {
    pkceCodeVerifier: flow.codeVerifier,
    expectedState: flow.state,
  })
  const claims = tokens.claims()! // sub, email, name, …

  // start your own session here: store claims.sub, and keep the refresh token server-side
  redirect('/')
}

Gérer la déconnexion

Mettez fin à la session de la personne dans votre app, puis envoyez-la vers le point de terminaison de fin de session d’Usher, pour que le Lobby l’oublie aussi.

app/auth/sign-out/route.ts
import * as client from 'openid-client'
import { redirect } from 'next/navigation'
import { usher } from '@/lib/usher'

export async function GET() {
  const config = await usher()
  // clear your own session first
  const url = client.buildEndSessionUrl(config, {
    post_logout_redirect_uri: process.env.APP_URL!,
  })
  redirect(url.href)
}

Avec le SDK

Une fois @usher/nextjs publié, la même app ressemblera à ceci. L’aperçu ci-dessous peut changer avant la sortie.

app/layout.tsx · app/page.tsx (aperçu)
// app/layout.tsx
import { UsherProvider } from '@usher/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <UsherProvider>{children}</UsherProvider>
      </body>
    </html>
  )
}

// app/page.tsx
import { SignIn, UserButton, currentUser } from '@usher/nextjs'

export default async function Page() {
  const user = await currentUser()
  return user ? <UserButton /> : <SignIn />
}

Étapes suivantes