encore-auth

Par encoredev · skills

Implémenter l'authentification avec des handlers et des gateways d'authentification dans Encore.ts.

npx skills add https://github.com/encoredev/skills --skill encore-auth

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/auth pour accéder aux données d'auth
  • getAuthData() retourne null dans 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

Skills similaires