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/callbackCopiez l’ID client et le secret client. Le secret n’est affiché qu’une seule fois.
Ajouter vos variables d’environnement
USHER_ISSUER=https://acme.usherid.com
USHER_CLIENT_ID=your_client_id
USHER_CLIENT_SECRET=your_client_secret
APP_URL=http://localhost:3000Installer 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.
pnpm add openid-clientDé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.
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.
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.
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.
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
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 />
}