Authentification Encore
Instructions
Encore.ts fournit un système d'authentification intégré pour identifier les appelants d'API et protéger les endpoints.
1. Créer un Auth Handler
// auth.ts
import { Header, Gateway } from "encore.dev/api";
import { authHandler } from "encore.dev/auth";
// Définir ce que le auth handler reçoit
interface AuthParams {
authorization: Header<"Authorization">;
}
// Définir ce auquel les requêtes authentifiées auront accès
interface AuthData {
userID: string;
email: string;
role: "admin" | "user";
}
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
// Valider le token (exemple avec JWT)
const token = params.authorization.replace("Bearer ", "");
const payload = await verifyToken(token);
if (!payload) {
throw APIError.unauthenticated("invalid token");
}
return {
userID: payload.sub,
email: payload.email,
role: payload.role,
};
}
);
// Enregistrer le auth handler avec une Gateway
export const gateway = new Gateway({
authHandler: auth,
});
2. Protéger les Endpoints
import { api } from "encore.dev/api";
// Endpoint protégé - authentification requise
export const getProfile = api(
{ method: "GET", path: "/profile", expose: true, auth: true },
async (): Promise<Profile> => {
// Seuls les utilisateurs authentifiés arrivent ici
}
);
// Endpoint public - pas d'authentification requise
export const healthCheck = api(
{ method: "GET", path: "/health", expose: true },
async () => ({ status: "ok" })
);
3. Accéder aux Données d'Auth dans les Endpoints
import { api } from "encore.dev/api";
import { getAuthData } from "~encore/auth";
export const getProfile = api(
{ method: "GET", path: "/profile", expose: true, auth: true },
async (): Promise<Profile> => {
const auth = getAuthData()!; // Non-null quand auth: true
return {
userID: auth.userID,
email: auth.email,
role: auth.role,
};
}
);
Comportement du Auth Handler
| Scénario | Handler retourne | Résultat |
|---|---|---|
| Identifiants valides | Objet AuthData |
Requête authentifiée |
| Identifiants invalides | Lève APIError.unauthenticated() |
Traité comme pas d'auth |
| Autre erreur | Lève une autre erreur | Requête interrompue |
Auth avec les Endpoints
| Config Endpoint | Requête a Auth | Résultat |
|---|---|---|
auth: true |
Oui | Procède avec données d'auth |
auth: true |
Non | 401 Unauthenticated |
auth: false ou omis |
Oui | Procède (données d'auth disponibles) |
auth: false ou omis |
Non | Procède (pas de données d'auth) |
Propagation d'Auth Service-to-Service
Les données d'auth se propagent automatiquement aux appels de services internes :
import { user } from "~encore/clients";
import { getAuthData } from "~encore/auth";
export const getOrderWithUser = api(
{ method: "GET", path: "/orders/:id", expose: true, auth: true },
async ({ id }): Promise<OrderWithUser> => {
const auth = getAuthData()!;
// L'auth est automatiquement propagée à cet appel
const orderUser = await user.getProfile();
return { order: await getOrder(id), user: orderUser };
}
);
Surcharger les Données d'Auth
Vous pouvez explicitement surcharger les données d'auth lors d'appels service-to-service :
import { user } from "~encore/clients";
// Surcharger les données d'auth pour cet appel spécifique
const adminUser = await user.getProfile(
{},
{ authData: { userID: "admin-123", email: "admin@example.com", role: "admin" } }
);
Patterns Courants d'Auth
Validation de Token JWT
import { jwtVerify } from "jose";
import { secret } from "encore.dev/config";
const jwtSecret = secret("JWTSecret");
async function verifyToken(token: string): Promise<JWTPayload | null> {
try {
const { payload } = await jwtVerify(
token,
new TextEncoder().encode(jwtSecret())
);
return payload;
} catch {
return null;
}
}
Authentification par API Key
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
const apiKey = params.authorization;
const user = await db.queryRow<User>`
SELECT id, email, role FROM users WHERE api_key = ${apiKey}
`;
if (!user) {
throw APIError.unauthenticated("invalid API key");
}
return {
userID: user.id,
email: user.email,
role: user.role,
};
}
);
Auth basée sur Cookie
interface AuthParams {
cookie: Header<"Cookie">;
}
export const auth = authHandler<AuthParams, AuthData>(
async (params) => {
const sessionId = parseCookie(params.cookie, "session");
if (!sessionId) {
throw APIError.unauthenticated("no session");
}
const session = await getSession(sessionId);
if (!session || session.expiresAt < new Date()) {
throw APIError.unauthenticated("session expired");
}
return {
userID: session.userID,
email: session.email,
role: session.role,
};
}
);
Tests avec Auth
Simuler l'authentification dans les tests en utilisant Vitest :
import { describe, it, expect, vi } from "vitest";
import * as auth from "~encore/auth";
import { getProfile } from "./api";
describe("authenticated endpoints", () => {
it("returns profile for authenticated user", async () => {
// Simuler getAuthData pour retourner un utilisateur de test
const spy = vi.spyOn(auth, "getAuthData");
spy.mockImplementation(() => ({
userID: "test-user-123",
email: "test@example.com",
role: "user",
}));
const profile = await getProfile();
expect(profile.email).toBe("test@example.com");
spy.mockRestore();
});
});
Directives
- Les auth handlers doivent être enregistrés avec une Gateway
- Utilisez
getAuthData()de~encore/authpour accéder aux données d'auth getAuthData()retournenulldans les requêtes non authentifiées- Les données d'auth se propagent automatiquement dans les appels service-to-service
- Levez
APIError.unauthenticated()pour des identifiants invalides - Gardez les auth handlers rapides - ils s'exécutent sur chaque requête authentifiée