Application Angular avec OpenID Connect
Sur cette page
Ce tutoriel connecte une application Angular à TOSIAM avec OpenID Connect, puis lui fait appeler une API Spring Boot protégée par les jetons d’accès que TOSIAM délivre. Il suit les recommandations actuelles pour une application qui tourne dans le navigateur : flux par code d’autorisation avec PKCE, client public (sans secret), déconnexion par le point de fin de session OpenID Connect.
Le code complet est dans le dépôt tosiam-samples, dossier tosiam-angular : une application Angular 22 (frontend, bibliothèque angular-oauth2-oidc) et une API d’albums photo Spring Boot 3.5 (backend).
Navigateur ── Angular (localhost:4200) ──① connexion, code + PKCE──▶ TOSIAM (localhost:8080, royaume ref)
│ ▲
└──② jeton d'accès──▶ API (localhost:8081) ──③ introspection (photos-api)Étape 1 : le fournisseur OAuth2 du royaume#
Dans le royaume ref, ajoutez le service OAuth2 Provider (voir Fournisseur OAuth2). Son émetteur (issuer) est http://localhost:8080/tosiam/oauth2/ref : l’application lit tout le reste (points d’accès, clés, déconnexion) dans le document de découverte http://localhost:8080/tosiam/oauth2/ref/.well-known/openid-configuration.
Deux réglages du fournisseur servent à ce tutoriel :
- Scopes acceptés (
supportedScopes) : déclarez les scopes avec leur description, affichée à l’utilisateur quand son consentement est demandé, par exempleopenid|Identifiant de connexion,profile|Nom et adresse e-mailetphotolibrary.read|Lecture de vos albums photo. - Autoriser les clients à passer outre le consentement (
clientsCanSkipConsent) : à activer pour que l’application, qui appartient au même éditeur que l’API, ne demande pas de consentement (étape suivante).
Étape 2 : le client de l’application#
Dans Clients, Créer un agent ouvre l’assistant. Donnez l’identifiant angular-openid-client et l’URI de redirection http://localhost:4200, l’adresse de l’application.
L’assistant demande un mot de passe à l’étape Sécurité : saisissez-en un quelconque, il ne servira pas. Gardez les types d’autorisation proposés, Authorization Code et Refresh Token, puis créez le client. Complétez ensuite sa fiche.
Onglet Général :
- Type de client :
Public. Une application qui s’exécute dans le navigateur ne peut pas garder de secret : son code d’autorisation est protégé par PKCE. - Scope(s) :
openid,profileetphotolibrary.read, le scope qu’exige l’API.
Onglet Avancé :
- Types de réponse : gardez seulement
code. - Méthode d’authentification au point d’accès des jetons :
client_secret_post. Un client public n’envoie que son identifiant, mais TOSIAM vérifie la méthode déclarée pour les clients OpenID Connect, et c’est sous cette méthode qu’il range une requête qui ne porte queclient_id. - Paramètre Code verifier requis : activé. Toute demande d’autorisation devra porter un
code_challengePKCE. - Consentement implicite : activé.
Onglet OpenID Connect : URI de redirection après déconnexion http://localhost:4200. Onglet Signature et chiffrement : algorithme de signature du jeton d’identité RS256, vérifiable avec les clés publiques du fournisseur.
Étape 3 : le client de l’API#
L’API reçoit des jetons d’accès opaques et demande à TOSIAM s’ils sont valides, au point d’introspection. Cette requête doit être authentifiée, et le client public de l’application n’a pas de secret : l’API a donc son propre client.
Créez un second client photos-api avec un secret (par exemple photos-api-secret pour ce test), de type Confidential, avec le seul scope am-introspect-all-tokens. Sans ce scope, un client ne peut introspecter que les jetons émis à son propre nom ; avec lui, il peut vérifier ceux de l’application.
Étape 4 : autoriser les appels depuis le navigateur (CORS)#
L’application appelle TOSIAM depuis le navigateur (document de découverte, point d’accès des jetons) : TOSIAM doit accepter son origine. Dans Services globaux › CORS configuration :
- activez Activation des restrictions CORS ;
- sous Sous-configurations, Nouvelle configuration : origine
http://localhost:4200, méthodesGET,POSTetOPTIONS, en-têtesauthorizationetcontent-type, configuration active.
La modification s’applique sans redémarrage. Pour vérifier :
curl -s -o /dev/null -D - -X OPTIONS http://localhost:8080/tosiam/oauth2/ref/access_token \
-H 'Origin: http://localhost:4200' -H 'Access-Control-Request-Method: POST' \
| grep -i access-controlLa réponse doit contenir Access-Control-Allow-Origin: http://localhost:4200.
Étape 5 : l’application Angular#
Les adresses sont regroupées dans frontend/src/environments/environment.ts (et environment.development.ts, utilisé par ng serve) :
export const environment = {
issuer: 'http://localhost:8080/tosiam/oauth2/ref', // émetteur du royaume ref
clientId: 'angular-openid-client', // client public, sans secret
apiBaseUrl: 'http://localhost:8081', // API qui reçoit le jeton d'accès
};La configuration OpenID Connect, frontend/src/app/sso.config.ts, s’en sert :
export const authConfig: AuthConfig = {
issuer: environment.issuer,
redirectUri: window.location.origin,
postLogoutRedirectUri: window.location.origin,
clientId: environment.clientId,
responseType: 'code',
scope: 'openid profile photolibrary.read',
oidc: true,
requireHttps: 'remoteOnly',
showDebugInformation: false,
};Pas de secret, et PKCE n’a pas besoin d’être demandé : la bibliothèque l’utilise par défaut avec le flux par code.
frontend/src/app/app.config.ts déclare le client OAuth et l’API qui doit recevoir le jeton :
provideHttpClient(withInterceptorsFromDi()),
provideOAuthClient({
resourceServer: {
allowedUrls: [environment.apiBaseUrl],
sendAccessToken: true,
},
}),L’intercepteur de la bibliothèque ajoute alors Authorization: Bearer <jeton d'accès> à chaque requête vers apiBaseUrl : le service photos.service.ts ne fait que des appels HttpClient ordinaires. withInterceptorsFromDi() est indispensable : sans lui, l’intercepteur n’est jamais appelé et l’API répond 401.
Le composant principal (app.component.ts) démarre le client :
this.oauthService.configure(authConfig);
this.oauthService.setupAutomaticSilentRefresh(); // renouvelle le jeton avec le refresh token
this.oauthService.loadDiscoveryDocumentAndTryLogin(); // échange le code au retour de TOSIAMlogin()appelleinitLoginFlow(): redirection vers TOSIAM aveccode_challengeetcode_challenge_method=S256.- Au retour,
loadDiscoveryDocumentAndTryLogin()échange le code contre les jetons, avec lecode_verifiergardé dans le navigateur. logout()appellelogOut(): redirection vers le point de fin de session de TOSIAM (/connect/endSession) avec le jeton d’identité, puis retour surpostLogoutRedirectUri.
La page d’accueil salue l’utilisateur par son nom. Le jeton d’identité identifie l’utilisateur (sub) mais ne contient pas son nom : comme le prévoit OpenID Connect quand un jeton d’accès est délivré, les informations du scope profile se lisent au point userinfo, avec ce jeton :
this.oauthService.loadUserProfile().then(profile => {
const info = (profile as { info?: Record<string, any> }).info;
this.userName.set(info?.['name'] || [info?.['given_name'], info?.['family_name']].filter(Boolean).join(' '));
});Étape 6 : l’API Spring Boot#
L’API est un serveur de ressources Spring Security (Spring Boot 3.5). backend/src/main/resources/application.properties désigne le point d’introspection du royaume et les identifiants du client photos-api :
server.port: 8081
app.cors.allowed-origin: http://localhost:4200
app.images.base-url: http://localhost:8081
spring.security.oauth2.resourceserver.opaquetoken.introspection-uri: http://localhost:8080/tosiam/oauth2/ref/introspect
spring.security.oauth2.resourceserver.opaquetoken.client-id: photos-api
spring.security.oauth2.resourceserver.opaquetoken.client-secret: ${PHOTOS_API_SECRET:photos-api-secret}La configuration de sécurité exige le scope photolibrary.read sur toute l’API :
http.cors(Customizer.withDefaults())
.authorizeHttpRequests(authz -> authz
.requestMatchers("/montagnes/**", "/voitures/**", "/animaux/**", "/culinaires/**").permitAll()
.requestMatchers("/fakealbums/**").hasAuthority("SCOPE_photolibrary.read")
.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(Customizer.withDefaults()));Spring Security envoie chaque jeton reçu au point d’introspection et convertit les scopes de la réponse en autorisations SCOPE_…. Un jeton expiré, révoqué ou sans le scope donne 401 ou 403.
Étape 7 : lancer et tester#
Dans deux terminaux :
cd tosiam-samples/tosiam-angular/frontend
npm install
npx ng serve # http://localhost:4200cd tosiam-samples/tosiam-angular/backend
./mvnw spring-boot:run # http://localhost:8081Ouvrez http://localhost:4200 et cliquez sur Login.
L’application redirige vers TOSIAM, qui affiche sa page de connexion pour le royaume ref. Connectez-vous avec un utilisateur du royaume.
De retour dans l’application, la page salue l’utilisateur par son nom et affiche les albums renvoyés par l’API.
Dans les outils de développement du navigateur, l’onglet Réseau montre les échanges :
| Requête | Ce qu’elle porte |
|---|---|
GET /tosiam/oauth2/ref/authorize | response_type=code, code_challenge, code_challenge_method=S256, nonce, state |
POST /tosiam/oauth2/ref/access_token | grant_type=authorization_code, code, code_verifier, client_id ; la réponse contient access_token, id_token et refresh_token |
GET /tosiam/oauth2/ref/userinfo | Authorization: Bearer <jeton d'accès> ; la réponse contient name, given_name, family_name |
GET localhost:8081/fakealbums/albums | Authorization: Bearer <jeton d'accès> |
GET /tosiam/oauth2/ref/connect/endSession | À la déconnexion : id_token_hint et post_logout_redirect_uri |
Après Logout, cliquer de nouveau sur Login redemande l’identifiant et le mot de passe : la session TOSIAM est bien fermée.
En cas de problème#
| Symptôme | Cause probable |
|---|---|
ng serve refuse de démarrer (« Node.js version … detected ») | Version de Node.js trop ancienne pour Angular 22 : il faut 24.15 ou plus |
| Erreur CORS dans la console du navigateur | Origine absente de la configuration CORS, ou Activation des restrictions CORS désactivée |
invalid_client « Invalid authentication method for accessing this endpoint » | Méthode d’authentification du client autre que client_secret_post |
| Erreur 500 dès la redirection vers TOSIAM | PKCE obligatoire, mais demande sans code_challenge (disablePKCE: true dans la configuration Angular) |
| Erreur 500 après Allow sur l’écran de consentement | Consentement implicite non activé (voir l’encadré de l’étape 2) |
L’API répond 401 | Identifiants photos-api incorrects, ou scope am-introspect-all-tokens manquant : l’introspection renvoie active: false. Ou jeton absent de la requête : withInterceptorsFromDi() manque dans app.config.ts |
| Pas de nom dans le message d’accueil | Appel userinfo refusé : en-tête authorization absent de la configuration CORS |
L’API répond 403 | Jeton sans le scope photolibrary.read |
En production#
Ce tutoriel tourne en local. Avant une mise en production :
- HTTPS partout : TOSIAM, l’application et l’API.
requireHttps: 'remoteOnly'refuse déjà un émetteur en HTTP hors delocalhost. - Les jetons vivent dans le navigateur (stockage de session par défaut), où un script injecté (XSS) pourrait les lire. Pour une application sensible, les recommandations actuelles (brouillon IETF OAuth 2.0 for Browser-Based Applications) préfèrent un back-end pour le front-end (BFF) : un petit serveur fait le flux OAuth, garde les jetons et ne donne au navigateur qu’un cookie de session
HttpOnly. - Introspection ou JWT : l’API interroge TOSIAM à chaque requête. Pour un trafic important, elle peut valider localement des jetons d’accès JWT avec les clés publiques du fournisseur (
jwks_uri), à condition de signer enRS256(voir Fournisseur OAuth2) ; un jeton révoqué reste alors accepté jusqu’à son expiration. - Consentement : le consentement implicite n’a de sens que pour vos propres applications. Une application tierce doit demander le consentement de l’utilisateur.
- Secrets : passez le secret de
photos-apipar la variable d’environnementPHOTOS_API_SECRET, pas dans le fichier de configuration.
Pour aller plus loin#
- Enregistrer un client : tous les réglages d’un client OAuth2.
- Fournisseur OAuth2 : signature des jetons, PKCE au niveau du royaume.
- Jetons : jetons opaques ou JWT, introspection et révocation.
Mis à jour le