見出し画像

Try!AngularとAuth0で楽々〜ユーザ認証

随分前から気になっていたAuth0。

ユーザ認証に関する実装は、Passportパッケージを使うとできるのですが、結構、めんどくさいんです。

そこで、Auth0。

ユーザ登録、ユーザ認証をまるっと任せられるサービスです。

全体像

Auth0を使うときの全体像は以下のような感じです。

画像1

Web Appl

開発するWebアプリケーション

Auth0

ログイン画面に表示するサービス名称やロゴなどの設定

Application

アプリケーション名の設定、ログインに必要なクライアントIDの参照、Callback URL、Logout URL設定

Database

ユーザ名の有無、パスワードの設定などのユーザ情報、認証情報の設定

Angularで実装

Augularでユーザ登録、ユーザ認証を実装します。

Auth0には、フレームワークごとに丁寧なチュートリアルがあります。

Angularプロジェクトの作成

Angularプロジェクトを作成します。

Routerを利用するように作成してください。

ng new authproj

認証パッケージのインストール

Auth0が提供する認証パッケージのインストールをインストールします。

npm install @auth0/auth0-spa-js --save

認証サービスの作成

Auth0で認証(ログイン、ログアウト)を行うサービスを実装します。

まず、authサービスを作成します。

ng generate service auth

作成したauthサービスを実装します(ほぼチュートリアルの通りです)

import { Injectable } from '@angular/core';
import createAuth0Client from '@auth0/auth0-spa-js';
import Auth0Client from '@auth0/auth0-spa-js/dist/typings/Auth0Client';
import { from, of, Observable, BehaviorSubject, combineLatest, throwError } from 'rxjs';
import { tap, catchError, concatMap, shareReplay } from 'rxjs/operators';
import { Router } from '@angular/router';

@Injectable({
 providedIn: 'root'
})
export class AuthService {
 // Domain
 static readonly AUTH0_DOMAIN: string = '<<Auth0のテナントドメイン>>';
 // Client ID
 static readonly AUTH0_CLIENT_ID: string = '<<Auth0のApplicationのClient ID>>';
 // Redirect URL
 static readonly AUTH0_REDIRECT_URL: string = 'http://localhost:4200';
 // Logout URL
 static readonly AUTH0_LOGOUT_URL: string = 'http://localhost:4200';

 // Create an observable of Auth0 instance of client
 auth0Client$ = (from(
   createAuth0Client({
     domain: AuthService.AUTH0_DOMAIN,
     client_id: AuthService.AUTH0_CLIENT_ID,
     redirect_uri: AuthService.AUTH0_REDIRECT_URL,
   })
 ) as Observable<Auth0Client>).pipe(
   shareReplay(1), // Every subscription receives the same shared value
   catchError(err => throwError(err))
 );

 // Define observables for SDK methods that return promises by default
 // For each Auth0 SDK method, first ensure the client instance is ready
 // concatMap: Using the client instance, call SDK method; SDK returns a promise
 // from: Convert that resulting promise into an observable
 isAuthenticated$ = this.auth0Client$.pipe(
   concatMap((client: Auth0Client) => from(client.isAuthenticated())),
   tap(res => this.loggedIn = res)
 );

 handleRedirectCallback$ = this.auth0Client$.pipe(
   concatMap((client: Auth0Client) => from(client.handleRedirectCallback()))
 );

 // Create subject and public observable of user profile data
 private userProfileSubject$ = new BehaviorSubject<any>(null);
 userProfile$ = this.userProfileSubject$.asObservable();
 // Create a local property for login status
 loggedIn: boolean = null;

 constructor(private router: Router) {
   // On initial load, check authentication state with authorization server
   // Set up local auth streams if user is already authenticated
   this.localAuthSetup();
   // Handle redirect from Auth0 login
   this.handleAuthCallback();
 }

 // When calling, options can be passed if desired
 // https://auth0.github.io/auth0-spa-js/classes/auth0client.html#getuser
 getUser$(options?): Observable<any> {
   return this.auth0Client$.pipe(
     concatMap((client: Auth0Client) => from(client.getUser(options))),
     tap(user => this.userProfileSubject$.next(user))
   );
 }

 private localAuthSetup() {
   // This should only be called on app initialization
   // Set up local authentication streams
   const checkAuth$ = this.isAuthenticated$.pipe(
     concatMap((loggedIn: boolean) => {
       if (loggedIn) {
         // If authenticated, get user and set in app
         // NOTE: you could pass options here if needed
         return this.getUser$();
       }
       // If not authenticated, return stream that emits 'false'
       return of(loggedIn);
     })
   );
   checkAuth$.subscribe();
 }

 login(redirectPath: string = '/') {
   // A desired redirect path can be passed to login method
   // (e.g., from a route guard)
   // Ensure Auth0 client instance exists
   this.auth0Client$.subscribe((client: Auth0Client) => {
     // Call method to log in
     client.loginWithRedirect({
       redirect_uri: `${window.location.origin}`,
       appState: { target: redirectPath }
     });
   });
 }

 private handleAuthCallback() {
   // Call when app reloads after user logs in with Auth0
   const params = window.location.search;
   if (params.includes('code=') && params.includes('state=')) {
     let targetRoute: string; // Path to redirect to after login processsed
     const authComplete$ = this.handleRedirectCallback$.pipe(
       // Have client, now call method to handle auth callback redirect
       tap(cbRes => {
         // Get and set target redirect route from callback results
         targetRoute = cbRes.appState && cbRes.appState.target ? cbRes.appState.target : '/';
       }),
       concatMap(() => {
         // Redirect callback complete; get user and login status
         return combineLatest([
           this.getUser$(),
           this.isAuthenticated$
         ]);
       })
     );
     // Subscribe to authentication completion observable
     // Response will be an array of user and login status
     authComplete$.subscribe(([user, loggedIn]) => {
       // Redirect to target route after callback processing
       this.router.navigate([targetRoute]);
     });
   }
 }

 logout() {
   // Ensure Auth0 client instance exists
   this.auth0Client$.subscribe((client: Auth0Client) => {
     // Call method to log out
     client.logout({
       client_id: AuthService.AUTH0_CLIENT_ID,
       returnTo: AuthService.AUTH0_LOGOUT_URL,
     });
   });
 }

}

認証するためのメニューの作成

認証するためのメニュー(NavBar)を作成します。

ログインしていなければ「ログイン」、ログインしていれば「ログアウト」を表示します。

<mdb-navbar SideClass="navbar navbar-expand-lg navbar-dark bg-primary" [containerInside]="false">
   <mdb-navbar-brand>
       <a class="navbar-brand" href="#">
           <p i18n="@@applTitle">for Scrum</p>
       </a>
   </mdb-navbar-brand>

   <links>
       <ul class="navbar-nav mr-auto">
           <li class="nav-item" *ngIf="!auth.loggedIn">
               <a class="nav-link waves-light" mdbWavesEffect (click)="auth.login()"><span i18n="@@loginTitle">ログイン</span></a>
           </li>
           <li class="nav-item" *ngIf="auth.loggedIn">
               <a class="nav-link waves-light" mdbWavesEffect (click)="auth.logout()"><span i18n="@@logoutTitle">ログアウト</span></a>
           </li>
       </ul>

       <ul class="navbar-nav ml-auto nav-flez-icons">
           <li class="nav-item">
               <a class="nav-link" [routerLink]="['profile']" *ngIf="auth.loggedIn">
                   <mdb-icon fas icon="user"></mdb-icon>
               </a>
           </li>
       </ul>
   </links>
</mdb-navbar>
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../auth.service';

@Component({
 selector: 'app-navbar',
 templateUrl: './nav-bar.component.html',
 styleUrls: ['./nav-bar.component.scss']
})
export class NavBarComponent implements OnInit {

 constructor(public auth: AuthService) { }

 ngOnInit() {
 }

}

実行します

ここまででユーザ登録、ユーザ認証の実装が終わったことになります。

実行してみます。

画像2

アカウントを登録していなければ、サインアップを実行します。

画像3

ログインしてみます。

画像4

ログインできました。

画像5

ほぼ、設定とチュートリアルにある実装で、ユーザ登録(サインアップ)、ユーザ認証(サインイン)を実装することができました。

ユーザ登録、認証については、セキュリティ的にしっかりとした実装が必要なのですが、差別化しにくい部分ですので、Auth0などのサービスを活用して、コア開発に集中するといいですね。

この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!
生涯現役なデベロッパーを目指しています。Typescript、Javascript、Salesforceのフルスタックデベロッパーとして日々コードを綴っています。コードを綴る中で、「これは!?」と思ったものを記事にしています。
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。