きっと何者にもなれないSEの記録帳

日々で得たIT技術を記録していきます。

Auth0 + Angularでの認証機能実装

Auth0を利用して、Angularで作成したアプリに認証機能を実装してみました。

auth0.com

Auth0のドキュメントにAngular向けのQuickStartがあるので、そのまま実装すればAuth0を利用できます。

Auth0 Angular 2+ SDK Quickstarts: Login

今回はAuth0で用意されているログイン画面の利用と認証後に扱うことが可能なユーザ情報の表示をやってみました。

ログイン

Auth0 Angular 2+ SDK Quickstarts: Login

つまずくポイントとしては、Auth0のログイン画面から戻ってきた場合にルートコンポーネントのコンストラクタが呼ばれることです。
ngOnInitメソッドは呼ばれないようです。
下記のようにルートコンポーネントで認証後の処理を行なっています。

import { Component } from '@angular/core';
import { AuthService } from './auth/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

  constructor(public auth: AuthService) {
    auth.handleAuthentication();
  }

}

ユーザ情報の利用

Auth0 Angular 2+ SDK Quickstarts: User Profile

QuickStartのコードの場合、認証後に別コンポーネント上でユーザの情報を表示するという作りになっています。
実用的なパターンとしては、認証済みであれば、ヘッダー部にユーザ情報を表示するというケースがあるかなと思いますので、それを実現するように作り変えてみました。

GitHub - hi1280/auth0-angular-samples

app.component.tsで認証及びユーザ情報の表示を行なっています。

app.component.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  profile: any;

  constructor(public auth: AuthService) {
    auth.handleAuthentication(() => {
      this.initProfile();
    });
  }

  ngOnInit() {
    this.initProfile();
  }

  logout() {
    this.profile = undefined;
    this.auth.logout();
  }

  initProfile() {
    if (this.auth.userProfile) {
      this.profile = this.auth.userProfile;
    } else {
      if (this.auth.isAuthenticated()) {
        this.auth.getProfile((err: any, profile: any) => {
          this.profile = profile;
        });
      }
    }
  }

}

auth.service.ts

import { Injectable } from '@angular/core';
import { AUTH_CONFIG } from './auth0-variables';
import { Router } from '@angular/router';
import 'rxjs/add/operator/filter';
import * as auth0 from 'auth0-js';

@Injectable()
export class AuthService {

  auth0 = new auth0.WebAuth({
    clientID: AUTH_CONFIG.clientID,
    domain: AUTH_CONFIG.domain,
    responseType: 'token id_token',
    audience: `https://${AUTH_CONFIG.domain}/userinfo`,
    redirectUri: AUTH_CONFIG.callbackURL,
    scope: 'openid profile'
  });

  userProfile: any;

  constructor(public router: Router) {}

  public login(): void {
    this.auth0.authorize();
  }

  public handleAuthentication(cb: any): void {
    this.auth0.parseHash((err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        window.location.hash = '';
        this.setSession(authResult);
        cb();
        this.router.navigate(['/home']);
      } else if (err) {
        this.router.navigate(['/home']);
        console.log(err);
        alert(`Error: ${err.error}. Check the console for further details.`);
      }
    });
  }

  public getProfile(cb): void {
    const accessToken = localStorage.getItem('access_token');
    if (!accessToken) {
      throw new Error('Access token must exist to fetch profile');
    }

    const self = this;
    this.auth0.client.userInfo(accessToken, (err, profile) => {
      if (profile) {
        self.userProfile = profile;
      }
      cb(err, profile);
    });
  }

  private setSession(authResult): void {
    // Set the time that the access token will expire at
    const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + new Date().getTime());
    localStorage.setItem('access_token', authResult.accessToken);
    localStorage.setItem('id_token', authResult.idToken);
    localStorage.setItem('expires_at', expiresAt);
  }

  public logout(): void {
    this.userProfile = undefined;
    // Remove tokens and expiry time from localStorage
    localStorage.removeItem('access_token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('expires_at');
    // Go back to the home route
    this.router.navigate(['/']);
  }

  public isAuthenticated(): boolean {
    // Check whether the current time is past the
    // access token's expiry time
    const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
    return new Date().getTime() < expiresAt;
  }

}

handleAuthenticationメソッドにコールバック関数を渡して、認証後にユーザ情報の取得を行なっているのが修正のポイントです。

まとめ

Auth0を利用すれば、GoogleFacebookTwitterといったソーシャルな認証プロバイダーを利用して、ユーザ情報を取得できるため、面倒な認証処理の実装がショートカットできます。
APIの保護などもできるようなので、認証にまつわる面倒ごとを色々省くことができるようです。

Angularアプリケーションプログラミング

Angularアプリケーションプログラミング