@obsidiane/auth-sdk
v0.1.56
Published
SDK JavaScript/TypeScript pour consommer l’API Obsidiane Auth depuis un navigateur :
Readme
@obsidiane/auth-sdk – SDK JS navigateur pour Obsidiane Auth
SDK JavaScript/TypeScript pour consommer l’API Obsidiane Auth depuis un navigateur :
- gère automatiquement les cookies (
__Secure-at,__Host-rt) viafetch+credentials: 'include'; - génère et envoie un token CSRF stateless dans l’en‑tête
csrf-tokenpour les endpoints sensibles ; - expose des sous‑clients dédiés (
auth,users,invites,setup) avec une méthode par endpoint ; - fournit des types TypeScript et une gestion d’erreurs structurée (
ApiError).
Ce SDK est conçu pour être utilisé côté navigateur uniquement (pas de Node côté serveur).
1. Installation
npm install @obsidiane/auth-sdk
# ou
yarn add @obsidiane/auth-sdkLe package est publié en ESM et inclut les définitions TypeScript (.d.ts).
Build (depuis le dépôt monorepo)
Depuis la racine du projet auth :
cd packages/auth-client-js
npm install # si nécessaire
npm run build # génère dist/*ou via un script dans le package.json racine :
{
"scripts": {
"auth-sdk:build": "npm --prefix packages/auth-client-js run build"
}
}2. Concepts clés
2.1 Cookies & sessions JWT
- L’API Auth émet deux cookies :
__Secure-at: access token (JWT), SameSiteLax, partagé ;__Host-rt: refresh token (Gesdinet), SameSiteStrict, host-only.
- Le SDK ne manipule pas directement ces tokens :
- ils restent dans les cookies HTTP Only ;
- les appels se font toujours avec
credentials: 'include'.
2.2 CSRF stateless
- Aucun token CSRF n’est généré par le backend.
- Le SDK doit :
- générer un jeton aléatoire par requête sensible ;
- l’envoyer dans l’en‑tête
csrf-token.
- Ce comportement est intégré dans le client HTTP :
csrf: truedans les options de requête → génération automatique d’un token ;csrf: 'valeur'→ utilisation d’un token fourni ;csrf: false/undefined→ aucun header CSRF.
2.3 Base URL & CORS
baseUrldoit pointer vers la racine d’Obsidiane Auth, par ex. :https://auth.example.comhttp://localhost:8000
- Côté navigateur, pensez à :
- autoriser l’origine dans
ALLOWED_ORIGINScôté backend ; - toujours activer
credentials: 'include'(géré par le SDK).
- autoriser l’origine dans
3. AuthClient – configuration de base
Le point d’entrée du SDK est la classe AuthClient.
import { AuthClient } from '@obsidiane/auth-sdk';
const auth = new AuthClient({
baseUrl: 'https://auth.example.com',
});3.1 Options disponibles
type AuthClientOptions = {
baseUrl: string; // requis
defaultHeaders?: HeadersInit; // en-têtes appliqués à toutes les requêtes
credentials?: RequestCredentials; // 'include' (défaut), 'same-origin', 'omit'
csrfHeaderName?: string; // 'csrf-token' par défaut
csrfTokenGenerator?: () => string; // pour surcharger la génération CSRF
fetchImpl?: typeof fetch; // pour tests ou environnements custom
};baseUrl: URL du service d’authentification.defaultHeaders: ex.{'X-App': 'my-frontend'}.credentials: par défaut'include'pour envoyer/recevoir les cookies JWT.csrfHeaderName: à ne changer que si le backend n’utilise pascsrf-token.csrfTokenGenerator: permet d’imposer votre propre stratégie de génération.fetchImpl: utile pour :- mocker
fetchdans des tests ; - injecter une version instrumentée de
fetch.
- mocker
4. Sous-clients & endpoints
AuthClient expose plusieurs sous‑clients pour regrouper les endpoints par domaine.
const client = new AuthClient({ baseUrl: 'https://auth.example.com' });
client.auth; // endpoints /api/auth/*
client.users; // endpoints /api/users*
client.invites; // endpoints /api/invite_users*
client.setup; // endpoint /api/setup/adminLes types suivants sont exportés pour typer vos appels :
import type {
// Auth
User,
UserRead,
LoginResponse,
MeResponse,
RegisterInput,
RegisterResponse,
RefreshResponse,
PasswordForgotResponse,
InviteStatusResponse,
CompleteInviteResponse,
InitialAdminInput,
InitialAdminResponse,
// JSON-LD
Item,
Collection,
// Resources API Platform
InviteUserRead,
} from '@obsidiane/auth-sdk';4.1 Auth – client.auth
Endpoints principaux :
POST /api/auth/loginGET /api/auth/mePOST /api/auth/refreshPOST /api/auth/registerPOST /api/auth/logoutPOST /api/auth/password/forgotPOST /api/auth/password/resetPOST /api/auth/invitePOST /api/auth/invite/complete
Méthodes exposées :
// Profil courant
const me: MeResponse = await client.auth.me();
// Login (CSRF requis)
const { user, exp }: LoginResponse = await client.auth.login(email, password);
// user.lastLoginAt contient la date ISO de dernière connexion (mise à jour à chaque login)
// Refresh silencieux (CSRF non requis, mais possible)
const refresh: RefreshResponse = await client.auth.refresh();
// Logout (CSRF requis)
await client.auth.logout();
// Inscription (CSRF requis)
const registration: RegisterResponse = await client.auth.register({
email: '[email protected]',
password: 'Secret123!',
});
// Demande de reset (CSRF requis)
const forgot: PasswordForgotResponse = await client.auth.requestPasswordReset('[email protected]');
// Reset password (CSRF requis)
await client.auth.resetPassword('<reset-token>', 'NewSecret123!');
// Invitation (admin, CSRF requis)
const inviteStatus: InviteStatusResponse = await client.auth.inviteUser('[email protected]');
// Compléter une invitation (public, CSRF requis)
const completed: CompleteInviteResponse = await client.auth.completeInvite('token', 'Secret123!');4.2 Setup initial – client.setup
Endpoint :
POST /api/setup/admin
const input: InitialAdminInput = {
email: '[email protected]',
password: 'Secret123!',
};
const created: InitialAdminResponse = await client.setup.createInitialAdmin(input);Ce flux n’est disponible que tant qu’aucun utilisateur n’existe dans la base.
4.3 Users – client.users
Endpoints Api Platform :
GET /api/usersGET /api/users/{id}DELETE /api/users/{id}POST /api/users/{id}/roles(admin, CSRF stateless)
Le backend expose les ressources utilisateur au format JSON-LD (API Platform) :
- une collection JSON-LD typée
Collection<UserRead>; - des items JSON-LD portant leurs métadonnées via
Item(@id,@type,@context).
import type { Collection, UserRead } from '@obsidiane/auth-sdk';
// Liste d’utilisateurs (admin uniquement)
const users: Collection<UserRead> = await client.users.list();
// Accès aux métadonnées JSON-LD de la collection
console.log(users['@id'], users.totalItems, users.view);
// Détail des items JSON-LD
const first: UserRead | undefined = users.member[0];
// Détail d’un utilisateur seul
const user: UserRead = await client.users.get(1);
// Mise à jour des rôles (admin + CSRF stateless automatique)
const updated = await client.users.updateRoles(1, { roles: ['ROLE_ADMIN'] });
console.log(updated.user.roles);
// Suppression via l’ID numérique
await client.users.delete(42);4.4 Invites – client.invites
Endpoints Api Platform :
GET /api/invite_usersGET /api/invite_users/{id}
Les invitations admin exposées par API Platform sont également renvoyées en JSON-LD.
import type { Collection, InviteUserRead } from '@obsidiane/auth-sdk';
// Collection JSON-LD d’invitations
const invites: Collection<InviteUserRead> = await client.invites.list();
// Accès aux items
const firstInvite: InviteUserRead | undefined = invites.member[0];
// Lecture d’une invitation spécifique
const invite: InviteUserRead = await client.invites.get('some-id');5. Gestion des erreurs
Toutes les erreurs HTTP (codes 4xx/5xx) sont mappées sur une exception ApiError :
import { ApiError } from '@obsidiane/auth-sdk';
try {
await client.auth.register({ email, password });
} catch (error) {
if (error instanceof ApiError) {
console.error('Status HTTP :', error.status);
console.error('Code métier :', error.errorCode);
console.error('Détails :', error.details);
console.error('Payload brut :', error.payload);
} else {
console.error('Erreur inattendue :', error);
}
}ApiError reflète la structure d’erreur du backend :
status: code HTTP ;errorCode: champerrordu payload (EMAIL_ALREADY_USED,INVALID_REGISTRATION, etc.) ;details: champdetailslorsque présent ;payload: payload complet retourné par l’API.
Un alias AuthSdkError est également exporté pour des usages plus génériques.
6. Intégration front – exemples
6.1 Vanilla JS / TS
import { AuthClient } from '@obsidiane/auth-sdk';
const client = new AuthClient({
baseUrl: 'https://auth.example.com',
});
async function handleLogin(email: string, password: string) {
const { user } = await client.auth.login(email, password);
console.log('Connecté en tant que', user.email);
}6.2 React (hook simple)
import { useState, useEffect } from 'react';
import { AuthClient, ApiError, type MeResponse } from '@obsidiane/auth-sdk';
const client = new AuthClient({ baseUrl: 'https://auth.example.com' });
export function useCurrentUser() {
const [data, setData] = useState<MeResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<ApiError | null>(null);
useEffect(() => {
let cancelled = false;
client.auth
.me()
.then((res) => {
if (!cancelled) {
setData(res);
}
})
.catch((err) => {
if (!cancelled && err instanceof ApiError && err.status === 401) {
setData(null);
} else if (!cancelled) {
setError(err);
}
})
.finally(() => {
if (!cancelled) {
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, []);
return { data, loading, error };
}6.3 Refresh silencieux
async function scheduleRefresh(client: AuthClient, exp: number) {
const now = Date.now() / 1000;
const delaySeconds = Math.max(exp - now - 30, 10); // rafraîchit 30s avant l’expiration
setTimeout(async () => {
const { exp: newExp } = await client.auth.refresh();
await scheduleRefresh(client, newExp);
}, delaySeconds * 1000);
}
const { exp } = await client.auth.login('[email protected]', 'Secret123!');
scheduleRefresh(client, exp);7. Utilisation avancée
7.1 Générer un token CSRF compatible
Dans la plupart des cas, vous n’avez pas besoin d’appeler cela directement (les méthodes du SDK s’en chargent). Pour des appels HTTP personnalisés, vous pouvez générer un token compatible :
import { AuthClient } from '@obsidiane/auth-sdk';
const client = new AuthClient({ baseUrl: 'https://auth.example.com' });
const csrf = client.generateCsrfToken();
await fetch('https://auth.example.com/api/custom/endpoint', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'csrf-token': csrf,
},
body: JSON.stringify({ foo: 'bar' }),
});7.2 Surcharger fetch
Pour instrumenter toutes les requêtes du SDK (logs, tracing, mocking) :
const instrumentedFetch: typeof fetch = async (input, init) => {
const start = performance.now();
const response = await fetch(input, init);
const duration = performance.now() - start;
console.debug('Auth SDK request', { input, status: response.status, duration });
return response;
};
const client = new AuthClient({
baseUrl: 'https://auth.example.com',
fetchImpl: instrumentedFetch,
});7.3 Surcharger la génération CSRF
const client = new AuthClient({
baseUrl: 'https://auth.example.com',
csrfTokenGenerator: () => myCustomRandomHex(32),
});8. Bonnes pratiques de sécurité côté front
- Ne stockez jamais les JWT dans
localStorageousessionStorage:- laissez le backend gérer les cookies
HttpOnly; - utilisez toujours
credentials: 'include'(géré par le SDK).
- laissez le backend gérer les cookies
- Générer un token CSRF par requête sensible (le SDK le fait pour toutes ses méthodes).
- Garder
baseUrlcohérent avec la politique CORS (ALLOWED_ORIGINS) du backend. - En production :
- servir le frontend en HTTPS ;
- s’assurer que les cookies
Securesont bien activés.
Pour le détail fonctionnel des endpoints (payloads, codes d’erreurs métier, flows complets), reportez‑vous au README.md du projet principal Obsidiane Auth. Ce SDK en est un simple wrapper typé, respectant les mêmes conventions (CSRF stateless, JWT + refresh, allowlist de redirection).
