Aller au contenu principal

Comment valider les jetons d’accès dans votre service API ou backend

La validation des jetons d’accès (Access tokens) est une étape essentielle pour appliquer le contrôle d’accès basé sur les rôles (RBAC) dans Logto. Ce guide vous accompagne dans la vérification des JWT émis par Logto dans votre backend / API, en contrôlant la signature, l’émetteur (Issuer), l’audience (Audience), l’expiration, les permissions (Portées / Scopes), et le contexte d’organisation.

Avant de commencer

Étape 1 : Initialiser les constantes et utilitaires

Définissez les constantes et utilitaires nécessaires dans votre code pour gérer l’extraction et la validation du jeton. Une requête valide doit inclure un en-tête Authorization sous la forme Bearer <jeton d’accès (access token)>.

auth-middleware.ts
import { IncomingHttpHeaders } from 'http';

const JWKS_URI = 'https://your-tenant.logto.app/oidc/jwks';
const ISSUER = 'https://your-tenant.logto.app/oidc';

export class AuthInfo {
constructor(
public sub: string,
public clientId?: string,
public organizationId?: string,
public scopes: string[] = [],
public audience: string[] = []
) {}
}

export class AuthorizationError extends Error {
name = 'AuthorizationError';
constructor(
message: string,
public status = 403
) {
super(message);
}
}

export function extractBearerTokenFromHeaders({ authorization }: IncomingHttpHeaders): string {
const bearerPrefix = 'Bearer ';

if (!authorization) {
throw new AuthorizationError('L’en-tête Authorization est manquant', 401);
}

if (!authorization.startsWith(bearerPrefix)) {
throw new AuthorizationError(
`L’en-tête Authorization doit commencer par "${bearerPrefix}"`,
401
);
}

return authorization.slice(bearerPrefix.length);
}

Étape 2 : Récupérer les informations sur votre tenant Logto

Vous aurez besoin des valeurs suivantes pour valider les jetons émis par Logto :

  • URI JSON Web Key Set (JWKS) : L’URL vers les clés publiques de Logto, utilisée pour vérifier les signatures JWT.
  • Émetteur (Issuer) : La valeur attendue de l’émetteur (l’URL OIDC de Logto).

Commencez par trouver l’endpoint de votre tenant Logto. Vous pouvez le trouver à différents endroits :

  • Dans la Console Logto, sous ParamètresDomaines.
  • Dans les paramètres de toute application que vous avez configurée dans Logto, ParamètresEndpoints & Credentials.

Récupérer depuis l’endpoint de découverte OpenID Connect

Ces valeurs peuvent être récupérées depuis l’endpoint de découverte OpenID Connect de Logto :

https://<your-logto-endpoint>/oidc/.well-known/openid-configuration

Voici un exemple de réponse (autres champs omis pour plus de clarté) :

{
"jwks_uri": "https://your-tenant.logto.app/oidc/jwks",
"issuer": "https://your-tenant.logto.app/oidc"
}

Puisque Logto ne permet pas de personnaliser l’URI JWKS ou l’émetteur (Issuer), vous pouvez coder ces valeurs en dur dans votre code. Cependant, cela n’est pas recommandé pour les applications en production, car cela peut augmenter la charge de maintenance si une configuration change à l’avenir.

  • URI JWKS : https://<your-logto-endpoint>/oidc/jwks
  • Émetteur (Issuer) : https://<your-logto-endpoint>/oidc

Étape 3 : Valider le jeton et les permissions

Après avoir extrait le jeton et récupéré la configuration OIDC, validez les éléments suivants :

  • Signature : Le JWT doit être valide et signé par Logto (via JWKS).
  • Émetteur (Issuer) : Doit correspondre à l’émetteur de votre tenant Logto.
  • Audience (Audience) : Doit correspondre à l’indicateur de ressource de l’API enregistré dans Logto, ou au contexte d’organisation si applicable.
  • Expiration : Le jeton ne doit pas être expiré.
  • Permissions (Portées / scopes) : Le jeton doit inclure les portées requises pour votre API / action. Les portées sont des chaînes séparées par des espaces dans la revendication scope.
  • Contexte d’organisation : Si vous protégez des ressources API au niveau organisation, validez la revendication organization_id.

Consultez JSON Web Token pour en savoir plus sur la structure et les revendications des JWT.

À vérifier selon chaque modèle de permission

  • Revendication Audience (aud) : Indicateur de ressource API
  • Revendication Organisation (organization_id) : Non présent
  • Portées (permissions) à vérifier (scope) : Permissions de ressource API

Pour les permissions d’organisation hors API, le contexte d’organisation est représenté par la revendication aud (par exemple, urn:logto:organization:abc123). La revendication organization_id n’est présente que pour les jetons de ressource API au niveau organisation.

astuce:

Validez toujours à la fois les permissions (portées / scopes) et le contexte (audience, organisation) pour sécuriser les API multi-tenant.

Ajouter la logique de validation

Nous utilisons jose dans cet exemple pour valider le JWT. Installez-le si ce n'est pas déjà fait :

npm install jose

Ou utilisez votre gestionnaire de paquets préféré (par exemple, pnpm ou yarn).

Commencez par ajouter ces utilitaires partagés pour gérer la validation du JWT :

jwt-validator.ts
import { createRemoteJWKSet, jwtVerify, JWTPayload } from 'jose';
import { AuthInfo, AuthorizationError } from './auth-middleware.js';

const jwks = createRemoteJWKSet(new URL(JWKS_URI));

export async function validateJwt(token: string): Promise<JWTPayload> {
const { payload } = await jwtVerify(token, jwks, {
issuer: ISSUER,
});

verifyPayload(payload);
return payload;
}

export function createAuthInfo(payload: JWTPayload): AuthInfo {
const scopes = (payload.scope as string)?.split(' ') ?? [];
const audience = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];

return new AuthInfo(
payload.sub!,
payload.client_id as string,
payload.organization_id as string,
scopes,
audience
);
}

function verifyPayload(payload: JWTPayload): void {
// Implémentez ici votre logique de vérification basée sur le modèle de permission
// Ceci sera détaillé dans la section sur les modèles de permission ci-dessous
}

Ensuite, implémentez le middleware pour vérifier le jeton d’accès (Access token) :

auth-middleware.ts
import { Request, Response, NextFunction } from 'express';
import { validateJwt, createAuthInfo } from './jwt-validator.js';

// Étendre l'interface Request d'Express pour inclure auth
declare global {
namespace Express {
interface Request {
auth?: AuthInfo;
}
}
}

export async function verifyAccessToken(req: Request, res: Response, next: NextFunction) {
try {
const token = extractBearerTokenFromHeaders(req.headers);
const payload = await validateJwt(token);

// Stocker les informations d'authentification dans la requête pour un usage générique
req.auth = createAuthInfo(payload);

next();
} catch (err: any) {
return res.status(err.status ?? 401).json({ error: err.message });
}
}

Selon votre modèle de permission, implémentez la logique de vérification appropriée dans jwt-validator.ts :

jwt-validator.ts
function verifyPayload(payload: JWTPayload): void {
// Vérifiez que la revendication d’audience correspond à votre indicateur de ressource API
const audiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
if (!audiences.includes('https://your-api-resource-indicator')) {
throw new AuthorizationError('Audience invalide');
}

// Vérifiez les portées requises pour les ressources API globales
const requiredScopes = ['api:read', 'api:write']; // Remplacez par vos portées requises
const scopes = (payload.scope as string)?.split(' ') ?? [];
if (!requiredScopes.every((scope) => scopes.includes(scope))) {
throw new AuthorizationError('Portée insuffisante');
}
}

Étape 4 : Appliquer le middleware à votre API

Appliquez le middleware à vos routes API protégées.

app.ts
import express from 'express';
import { verifyAccessToken } from './auth-middleware.js';

const app = express();

app.get('/api/protected', verifyAccessToken, (req, res) => {
// Accédez directement aux informations d'authentification depuis req.auth
res.json({ auth: req.auth });
});

app.get('/api/protected/detailed', verifyAccessToken, (req, res) => {
// Votre logique de point de terminaison protégé
res.json({
auth: req.auth,
message: 'Données protégées accessibles avec succès',
});
});

app.listen(3000);

Étape 5 : Tester votre implémentation

Obtenir des jetons d’accès (Access tokens)

Depuis votre application cliente :
Si vous avez configuré une intégration client, votre application peut obtenir automatiquement les jetons. Extrayez le jeton d’accès (access token) et utilisez-le dans les requêtes API.

Pour tester avec curl / Postman :

  1. Jetons utilisateur : Utilisez les outils développeur de votre application cliente pour copier le jeton d’accès depuis le localStorage ou l’onglet réseau.

  2. Jetons machine à machine : Utilisez le flux d’identifiants client (client credentials flow). Voici un exemple non normatif utilisant curl :

    curl -X POST https://your-tenant.logto.app/oidc/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=your-m2m-client-id" \
    -d "client_secret=your-m2m-client-secret" \
    -d "resource=https://your-api-resource-indicator" \
    -d "scope=api:read api:write"

    Vous devrez peut-être ajuster les paramètres resource et scope selon votre ressource API (API resource) et vos permissions ; un paramètre organization_id peut également être requis si votre API est liée à une organisation.

astuce:

Besoin d’inspecter le contenu du jeton ? Utilisez notre décodificateur JWT pour décoder et vérifier vos JWT.

Tester les points de terminaison protégés

Requête avec jeton valide
curl -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." \
http://localhost:3000/api/protected

Réponse attendue :

{
"auth": {
"sub": "user123",
"clientId": "app456",
"organizationId": "org789",
"scopes": ["api:read", "api:write"],
"audience": ["https://your-api-resource-indicator"]
}
}
Jeton manquant
curl http://localhost:3000/api/protected

Réponse attendue (401) :

{
"error": "Authorization header is missing"
}
Jeton invalide
curl -H "Authorization: Bearer invalid-token" \
http://localhost:3000/api/protected

Réponse attendue (401) :

{
"error": "Invalid token"
}

Tests spécifiques au modèle de permission

Scénarios de test pour les API protégées par des portées globales :

  • Portées valides : Testez avec des jetons qui incluent les portées API requises (par exemple, api:read, api:write)
  • Portées manquantes : Attendez-vous à une réponse 403 Interdit si le jeton ne contient pas les portées requises
  • Audience incorrecte : Attendez-vous à une réponse 403 Interdit si l’audience ne correspond pas à la ressource API
# Jeton sans les portées requises - attendre 403
curl -H "Authorization: Bearer token-without-required-scopes" \
http://localhost:3000/api/protected
Personnalisation des revendications de jeton JSON Web Token (JWT)

OpenID Connect Discovery

RFC 8707 : Indicateurs de ressource