@congo-digital/auth
v0.1.0
Published
Reusable CDS auth helpers for Keycloak/OIDC with React bindings and pluggable storage.
Readme
@congo-digital/auth
SDK TypeScript pour intégrer l'authentification CDS basée sur Keycloak / OpenID Connect, avec support PKCE, bindings React et helpers Expo / React Native.
Fonctionnalités
- Génération d'URL d'authentification OIDC avec PKCE
- Gestion du cycle
login,registeretreset-credentials - Échange du code d'autorisation contre des tokens
- Récupération du profil utilisateur via
userinfo - Rafraîchissement et vérification de token
- Adaptateurs de stockage pour navigateur, mémoire, AsyncStorage et Expo Secure Store
- Composants React prêts à l'emploi
- Helpers Expo pour lancer et traiter le callback
- Appels d'administration Keycloak pour supprimer un utilisateur
Installation
npm install @congo-digital/authSelon votre cible, ajoutez aussi les dépendances de pair utiles :
npm install react
npm install expo-secure-store @react-native-async-storage/async-storageConfiguration
Le client peut être configuré par code ou via variables d'environnement.
Règle de résolution
Lors de l'instanciation, le SDK suit cette logique :
- si une valeur est fournie dans
config, elle est prioritaire - sinon le SDK lit la variable d'environnement correspondante
- si une valeur obligatoire n'existe ni dans
configni dans l'environnement, une erreur est levée immédiatement
Exemple :
import { createCdsAuth } from "@congo-digital/auth";
const auth = createCdsAuth({
issuer: "https://your-keycloak.example.com/realms/your-realm",
clientId: "your-client-id",
callbackUrl: "https://your-app.example.com/api/auth/callback",
});Ici, même si aucune variable d'environnement n'est définie, l'instance fonctionne car les valeurs requises sont passées en paramètre.
Variables d'environnement supportées
KEYCLOAK_ISSUER=https://your-keycloak.example.com/realms/your-realm
KEYCLOAK_CLIENT_ID=your-client-id
KEYCLOAK_CLIENT_SECRET=your-client-secret
KEYCLOAK_ADMIN_CLIENT_ID=your-admin-client-id
KEYCLOAK_ADMIN_CLIENT_SECRET=your-admin-client-secret
NEXT_PUBLIC_APP_URL=https://your-app.example.comConfiguration TypeScript
import { createCdsAuth } from "@congo-digital/auth";
const auth = createCdsAuth({
issuer: "https://your-keycloak.example.com/realms/your-realm",
clientId: "your-client-id",
appUrl: "https://your-app.example.com",
callbackPath: "/api/auth/callback",
});Paramètres requis si les variables d'environnement sont absentes
Si l'environnement ne contient pas les variables nécessaires, il faut fournir ces paramètres à l'instanciation :
issuersiKEYCLOAK_ISSUERest absentclientIdsiKEYCLOAK_CLIENT_IDest absentcallbackUrlouappUrlsiNEXT_PUBLIC_APP_URLest absent
Notes :
callbackUrlpeut être donné directement- sinon
appUrlpermet de construire automatiquement l'URL de callback aveccallbackPath clientSecret,adminClientIdetadminClientSecretrestent optionnels sauf si vous utilisez les fonctions qui en dépendent
Erreurs levées si la configuration est incomplète
L'instanciation échoue explicitement avec les messages suivants :
Missing KEYCLOAK_ISSUER or config.issuerMissing KEYCLOAK_CLIENT_ID or config.clientIdMissing callbackUrl or appUrl to derive it
Exemple invalide :
import { createCdsAuth } from "@congo-digital/auth";
// Lève une erreur si les variables d'environnement ne sont pas définies
const auth = createCdsAuth();Exemple valide sans variables d'environnement :
import { createCdsAuth } from "@congo-digital/auth";
const auth = createCdsAuth({
issuer: "https://your-keycloak.example.com/realms/your-realm",
clientId: "your-client-id",
callbackUrl: "https://your-app.example.com/api/auth/callback",
});Utilisation de base
1. Démarrer une authentification
import { createCdsAuth, createMemoryStorage } from "@congo-digital/auth";
const auth = createCdsAuth({
issuer: "https://your-keycloak.example.com/realms/your-realm",
clientId: "your-client-id",
callbackUrl: "https://your-app.example.com/api/auth/callback",
});
const storage = createMemoryStorage();
const { url } = await auth.startAuthorization({
action: "login",
storage,
});
// Rediriger l'utilisateur vers l'URL générée
console.log(url);2. Traiter le callback
const result = await auth.handleCallback({
currentUrl: "https://your-app.example.com/api/auth/callback?code=...&state=...",
storage,
});
console.log(result.tokens);
console.log(result.user);Adaptateurs de stockage
Le SDK stocke temporairement la transaction PKCE (state, codeVerifier) entre le démarrage du flux et le callback.
Navigateur
import { createBrowserSessionStorageAdapter } from "@congo-digital/auth";
const storage = createBrowserSessionStorageAdapter();Web Storage personnalisé
import { createWebStorageAdapter } from "@congo-digital/auth";
const storage = createWebStorageAdapter(window.localStorage);React Native AsyncStorage
import AsyncStorage from "@react-native-async-storage/async-storage";
import { createAsyncStorageAdapter } from "@congo-digital/auth";
const storage = createAsyncStorageAdapter(AsyncStorage);Expo Secure Store
import * as SecureStore from "expo-secure-store";
import { createExpoSecureStoreAdapter } from "@congo-digital/auth";
const storage = createExpoSecureStoreAdapter(SecureStore);React
Le point d'entrée @congo-digital/auth/react expose un bouton générique et trois variantes prêtes à l'emploi.
import { LoginButton } from "@congo-digital/auth/react";
export function SignIn() {
return (
<LoginButton
config={{
issuer: "https://your-keycloak.example.com/realms/your-realm",
clientId: "your-client-id",
callbackUrl: "https://your-app.example.com/api/auth/callback",
}}
className="btn"
onAuthError={(error) => console.error(error)}
>
Se connecter
</LoginButton>
);
}Exports React disponibles :
AuthButtonLoginButtonRegisterButtonForgotPasswordButton
Expo
Le point d'entrée @congo-digital/auth/expo fournit des helpers pour lancer le flux et traiter le callback.
import * as SecureStore from "expo-secure-store";
import { createCdsAuth, createExpoSecureStoreAdapter } from "@congo-digital/auth";
import { createExpoAuthFlow } from "@congo-digital/auth/expo";
const auth = createCdsAuth({
issuer: "https://your-keycloak.example.com/realms/your-realm",
clientId: "your-client-id",
callbackUrl: "myapp://auth/callback",
});
const storage = createExpoSecureStoreAdapter(SecureStore);
const flow = createExpoAuthFlow(auth, storage);
await flow.startLogin(async (url) => {
// Exemple: WebBrowser.openAuthSessionAsync(url, redirectUrl)
console.log(url);
});
const session = await flow.handleCallback("myapp://auth/callback?code=...&state=...");Vérification et rafraîchissement des tokens
const refreshed = await auth.refreshAccessToken(refreshToken);
const user = await auth.verifyAccessToken(refreshed.access_token);verifyAccessToken() renvoie null si le token est invalide.
Déconnexion
const logoutUrl = auth.getLogoutUrl(idToken);Administration Keycloak
Si adminClientId et adminClientSecret sont configurés, le client peut obtenir un token d'administration et supprimer un utilisateur :
await auth.deleteUser("user-id");Référence API
@congo-digital/auth
createCdsAuth(config?)
Crée une instance de CdsAuthClient.
Paramètres :
config?: CdsAuthConfigInput: configuration du client.
Comportement :
- si
configcontient les champs requis, ils sont utilisés directement - sinon les valeurs sont cherchées dans l'environnement
- si
issuer,clientIdou une valeur permettant de construirecallbackUrlsont absents, l'instanciation lève une erreur
Retour :
CdsAuthClient
new CdsAuthClient(config?)
Construit un client d'authentification et résout automatiquement la configuration via les variables d'environnement si nécessaire.
Paramètres :
config?: CdsAuthConfigInput
Propriétés :
config: ResolvedCdsAuthConfig: configuration finale utilisée par le client.
Erreurs possibles :
Missing KEYCLOAK_ISSUER or config.issuerMissing KEYCLOAK_CLIENT_ID or config.clientIdMissing callbackUrl or appUrl to derive it
auth.getAuthorizationUrl(action?, callbackUrl?)
Génère une URL OIDC avec PKCE sans persister la transaction.
Paramètres :
action?: AuthAction:"login","register"ou"reset-credentials". Défaut :"login".callbackUrl?: string: URL de redirection à utiliser à la place de celle configurée.
Retour :
Promise<AuthorizationResult>AuthorizationResult.url: URL à ouvrir.AuthorizationResult.state: état PKCE généré.AuthorizationResult.codeVerifier: vérificateur PKCE à conserver.
auth.startAuthorization(options)
Démarre le flux d'authentification et stocke la transaction PKCE dans le stockage fourni.
Paramètres :
options.action?: AuthAction: type d'action à lancer.options.storage: StorageAdapter: stockage temporaire de la transaction.options.callbackUrl?: string: URL de callback spécifique à cet appel.
Retour :
Promise<AuthorizationResult>
auth.exchangeCodeForTokens(code, codeVerifier, callbackUrl?)
Échange un code d'autorisation contre un jeu de tokens.
Paramètres :
code: string: code renvoyé par Keycloak.codeVerifier: string: PKCE verifier généré lors du démarrage du flux.callbackUrl?: string: URL de callback utilisée pendant l'échange.
Retour :
Promise<TokenSet>
auth.fetchUserInfo(accessToken)
Récupère le profil utilisateur depuis l'endpoint userinfo.
Paramètres :
accessToken: string: token d'accès valide.
Retour :
Promise<UserInfo>
auth.handleCallback(options)
Valide l'URL de callback, recharge la transaction stockée, échange le code puis récupère l'utilisateur.
Paramètres :
options.currentUrl: string: URL complète du callback reçue par l'application.options.storage: StorageAdapter: stockage contenant la transaction initiale.options.callbackUrl?: string: URL de callback à utiliser pour l'échange du code.
Retour :
Promise<CallbackResult>CallbackResult.tokens: tokens OIDC.CallbackResult.user: profil utilisateur.
auth.refreshAccessToken(refreshToken)
Rafraîchit un token d'accès à partir d'un refresh_token.
Paramètres :
refreshToken: string
Retour :
Promise<TokenSet>
auth.verifyAccessToken(accessToken)
Vérifie la signature et l'émetteur d'un token JWT via les certificats du realm.
Paramètres :
accessToken: string
Retour :
Promise<UserInfo | null>:nullsi le token est invalide.
auth.getLogoutUrl(idToken)
Construit l'URL de déconnexion Keycloak.
Paramètres :
idToken: string:id_tokende la session.
Retour :
string
auth.getAdminAccessToken()
Demande un token d'administration avec le flux client_credentials.
Pré-requis :
adminClientIdadminClientSecret
Retour :
Promise<string>
auth.deleteUser(userId)
Supprime un utilisateur via l'API d'administration Keycloak.
Paramètres :
userId: string: identifiant Keycloak de l'utilisateur.
Retour :
Promise<void>
resolveCdsAuthConfig(input?, env?)
Résout la configuration finale du SDK à partir d'une config partielle et d'un environnement.
Paramètres :
input?: CdsAuthConfigInputenv?: EnvLike: objet clé/valeur similaire àprocess.env.
Retour :
ResolvedCdsAuthConfig
Règles importantes :
input.issuerest prioritaire surKEYCLOAK_ISSUERinput.clientIdest prioritaire surKEYCLOAK_CLIENT_IDinput.callbackUrlest prioritaire sur toute valeur dérivée- si
callbackUrln'est pas fourni, il est construit à partir deappUrl+callbackPath - si aucune de ces données n'est disponible, la fonction lève une erreur
createMemoryStorage()
Crée un stockage en mémoire, utile pour les tests ou usages temporaires.
Paramètres :
- aucun
Retour :
StorageAdapter
createWebStorageAdapter(storage)
Adapte un stockage compatible Web Storage.
Paramètres :
storage: Pick<Storage, "getItem" | "setItem" | "removeItem">
Retour :
StorageAdapter
createBrowserSessionStorageAdapter()
Utilise window.sessionStorage automatiquement.
Paramètres :
- aucun
Retour :
StorageAdapter
createAsyncStorageAdapter(storage)
Adapte un stockage asynchrone de type React Native.
Paramètres :
storage: AsyncKeyValueStorage
Retour :
StorageAdapter
createExpoSecureStoreAdapter(storage)
Adapte Expo Secure Store au format attendu par le SDK.
Paramètres :
storage: ExpoSecureStoreLike
Retour :
StorageAdapter
@congo-digital/auth/react
AuthButton(props)
Bouton React générique qui démarre le flux d'authentification puis redirige le navigateur.
Props principales :
auth?: CdsAuthClient: instance existante à réutiliser.config?: CdsAuthConfigInput: configuration utilisée siauthn'est pas fourni.action?: AuthAction:"login","register"ou"reset-credentials".storage?: StorageAdapter: stockage de transaction. Par défaut :sessionStorage.callbackUrl?: string: URL de callback spécifique.children?: ReactNode: contenu du bouton.onAuthError?: (error: Error) => void: callback d'erreur....props: autres props HTML standard de<button>saufonClick.
LoginButton(props)
Alias de AuthButton avec action="login".
Paramètres :
props: Omit<AuthButtonProps, "action">
RegisterButton(props)
Alias de AuthButton avec action="register".
Paramètres :
props: Omit<AuthButtonProps, "action">
ForgotPasswordButton(props)
Alias de AuthButton avec action="reset-credentials".
Paramètres :
props: Omit<AuthButtonProps, "action">
@congo-digital/auth/expo
startExpoAuthorization(auth, options)
Démarre l'authentification puis appelle openUrl avec l'URL générée.
Paramètres :
auth: CdsAuthClientoptions.action?: AuthActionoptions.storage: StorageAdapteroptions.callbackUrl?: stringoptions.openUrl: (url: string) => Promise<void> | void
Retour :
Promise<string>: URL ouverte.
handleExpoCallback(auth, options)
Wrapper Expo autour de auth.handleCallback().
Paramètres :
auth: CdsAuthClientoptions.currentUrl: stringoptions.storage: StorageAdapteroptions.callbackUrl?: string
Retour :
Promise<CallbackResult>
createExpoAuthFlow(auth, storage, callbackUrl?)
Crée une API simplifiée pour Expo.
Paramètres :
auth: CdsAuthClientstorage: StorageAdaptercallbackUrl?: string
Retour :
- objet avec :
startLogin(openUrl)startRegister(openUrl)startForgotPassword(openUrl)handleCallback(currentUrl)
Développement
npm run buildLe package publie les fichiers compilés dans dist/.
Licence
MIT. Voir LICENSE.
