C’est un grand classique auquel on ne peut pas échapper : identifier les utilisateurs (le plus souvent avec un login / mot de passe)
de manière à leur proposer du contenu spécifique adapté.
Le problème c’est qu’il faut stocker ces identifiants de manière sécurisée, proposer des mécaniques pour récupérer des accès oubliés,
empêcher les bots de créer de faux comptes… Si on veut faire les choses bien, ça devient vite très compliqué. Et on n’a pas encore
parlé de scope, de token, de délégation…
Du coup, si on laissait cette partie compliqué et qui n’apporte rien d’un point de vue fonctionnel à quelqu’un qui sait le faire
de manière correct et nous concentrer sur ce qui nous intéresse un peu plus ?
Il existe plusieurs solutions sur le marché, pour ce tutoriel j’ai choisi de parler de Auth0.
J’aurais pu continuer sur ma lancée avec Firebase Authentication mais j’aime
varier les plaisirs, dans ce tutoriel on va donc s’intéresser à brancher cette solution sur notre application Angular,
on ne va donc pas traiter la partie backend (beaucoup moins compliqué) qui sera expliquée dans un futur tutoriel.
vous avez déjà une application Angular (avec du routing)
À partir de là on va voir comment créer une application pour tester.
Sur le dashboard de Auth0 on commence par crée une nouvelle application grâce au gros bouton orange “CREATE APPLICATION”
et on choisit une application de type “single page application”.
Il est tout à fait possible de suivre le “quick start guide” pour Angular fournit directement mais je vais présente
une version alternative ici.
On va se rendre dans l’onglet Settings pour avoir les informations qui nous intéressent.
Je vous conseille de laisser un onglet ouvert sur cette page, on va y revenir régulièrement.
On va commencer doucement à créer un service Angular responsable de l’authentification de nos utilisateurs.
Dans ce service Auth on va initialiser une instance de Auth0Client
qui nous servira à exécuter nos requêtes.
La création de ce client se faisant de manière asynchrone par la méthode createAuth0Client
on va mettre tout ça sous la forme d’un Observable RxJS et le mettre en cache pour éviter de le recréer à chaque fois.
Ça donne quelque chose dans ce genre-là :
1
2
3
4
5
6
7
8
9
10
// Create an observable of Auth0 instance of client
auth0Client$=from(createAuth0Client({domain:'my-domain',client_id:'my-client-id'})).pipe(shareReplay(1)// Every subscription receives the same shared value
);
Les valeurs domain et client_id sont à récupérer sur le dashboard de Auth0.
On va enfin rajouter quelques méthodes du client pour pouvoir les utiliser sour la forme d’Observable.
/**
* Performs a redirect to `/authorize` using the parameters
* provided as arguments. Random and secure `state` and `nonce`
* parameters will be auto-generated.
*/privateloginWithRedirect$(options?: RedirectLoginOptions):Observable<void>{returnthis.auth0Client$.pipe(concatMap(client=>from(client.loginWithRedirect(options))));}/**
* Clears the application session and performs a redirect to `/v2/logout`, using
* the parameters provided as arguments, to clear the Auth0 session.
* If the `federated` option is specified it also clears the Identity Provider session.
* If the `localOnly` option is specified, it only clears the application session.
* It is invalid to set both the `federated` and `localOnly` options to `true`,
* and an error will be thrown if you do.
* [Read more about how Logout works at Auth0](https://auth0.com/docs/logout).
*/privateinternalLogout$(options?: LogoutOptions):Observable<void>{returnthis.auth0Client$.pipe(tap(client=>{client.logout(options);}),ignoreElements());}/**
* Returns the user information if available (decoded
* from the `id_token`).
*/privategetUser$(options?: GetUserOptions):Observable<any>{returnthis.auth0Client$.pipe(concatMap(client=>from(client.getUser(options))));}/**
* Returns `true` if there's valid information stored,
* otherwise returns `false`.
*/isAuthenticated$():Observable<boolean>{returnthis.auth0Client$.pipe(concatMap(client=>from(client.isAuthenticated())));}/**
* If there's a valid token stored, return it. Otherwise, opens an
* iframe with the `/authorize` URL using the parameters provided
* as arguments. Random and secure `state` and `nonce` parameters
* will be auto-generated. If the response is successful, results
* will be valid according to their expiration times.
*/getTokenSilently$(options?: GetTokenSilentlyOptions):Observable<string>{returnthis.auth0Client$.pipe(concatMap(client=>from(client.getTokenSilently(options))));}/**
* After the browser redirects back to the callback page,
* call `handleRedirectCallback` to handle success and error
* responses from Auth0. If the response is successful, results
* will be valid according to their expiration times.
*/handleRedirectCallback$():Observable<RedirectLoginResult>{returnthis.auth0Client$.pipe(concatMap(client=>from(client.handleRedirectCallback())));}
et enfin dans le service on écrit une méthode pour effectuer le login
1
2
3
4
5
6
7
8
login$(redirectPath?: string):Observable<void>{// A desired redirect path can be passed to login method (e.g., from a route guard)
returnthis.loginWithRedirect$({appState:{target:(redirectPath?redirectPath : this.router.url)}});}
En appelant cette méthode loginWithRedirect
l’utilisateur va quitter votre application pour se retrouver sur une page d’authentification générée par Auth0
c’est ce qu’ils appellent Universal Login.
Il est possible de changer les couleurs et le logo pour le rendre un peu plus proche de votre application.
Lors de l’appel à la méthode on peut utiliser le paramètre appState pour stocker des informations propre à l’application
qui pourront être récupérée une fois que l’utilisateur reviendra sur l’application. Pour faire simplement ici il s’agit simplement
de l’URL à laquelle ramenée l’utilisateur.
J’ai mis l’URL actuelle de l’utilisateur par défaut (ce qui correspond au cas d’usage le plus courant) pour ne pas avoir
à passer ce paramètre systématiquement.
On teste notre super bouton et là… erreur…
Il va falloir qu’on configure une callback : une page sur laquelle l’utilisateur sera redirigé une fois la connexion terminée.
Cette page est différente de cette donnée dans le appState, cette callback est une URL unique pour notre application.
Une fois cette page de callback atteinte on se chargera de rediriger l’utilisateur vers la page qu’il désire.
1
2
3
4
5
6
7
8
// Create an observable of Auth0 instance of client
auth0Client$=from(createAuth0Client({domain:'my-domain',client_id:'my-client-id',redirect_uri:`${window.location.origin}/user/callback`}))
J’ai choisi ce path mais si vou préférez en utiliser un autre c’est tout à fait possible.
Il faut maintenant configurer cette URL de callback comme valide pour notre application
ainsi que les web origins autorisés.
Si vous exposez votre application sur internet, il faudra préciser l’URL pour tous les noms de domaines que vous utilisez.
Maintenant on peut enfin s’identifier et créer un compte. Malheureusement on ne gère pas encore correctement la callback
et il va falloir créer un composant pour ça.
exportclassCallbackComponentimplementsOnInit,OnDestroy{privatesubscription=newSubscription();constructor(privateauth: AuthService,privaterouter: Router){}ngOnInit():void{// Call when app reloads after user logs in with Auth0
constparams=window.location.search;if(!params.includes('code=')||!params.includes('state=')){this.router.navigateByUrl('/');return;}this.subscription.add(this.auth.handleRedirectCallback$().pipe(map(cbRes=>{// Get and set target redirect route from callback results
returncbRes.appState&&cbRes.appState.target?cbRes.appState.target:'/';}),concatMap((targetRoute)=>{// Redirect to target route after callback processing
returnthis.router.navigateByUrl(targetRoute);})).subscribe(_=>{},error=>{console.error(error);this.router.navigateByUrl('/');}));}ngOnDestroy():void{this.subscription.unsubscribe();}}
Ce code sert à utiliser les paramètres d’URL de la callback pour authentifier l’utilisateur et enfin le rediriger vers la page
qu’il a demandé.
On tente d’initialiser la valeur à la construction du service et on va modifier la méthode handleRedirectCallback pour que la valeur soit actualisée correctement.
Petit bonus, voici un guard Angular permettant de valider que la personne est bien authentifié qui qui permettra de
bloquer l’accès si ce n’est pas le cas.
Si vous voulez authentifié des appels HTTP vers votre API, vous pouvez également utiliser cet intercepteur, qui ajoutera
automatiquement le token à chaque requête.
Attention tout de même, si vous appelez d’autres API depuis votre application le token risque de fuiter. Il est
important de contrôler que l’URL appelée correspond bien à votre API.