![見出し画像](https://assets.st-note.com/production/uploads/images/27754517/rectangle_large_type_2_6280de662d7c5782ddc6fb3f4df102e6.jpg?width=1200)
Try!AngularとAuth0で楽々〜ユーザ認証
随分前から気になっていたAuth0。
ユーザ認証に関する実装は、Passportパッケージを使うとできるのですが、結構、めんどくさいんです。
そこで、Auth0。
ユーザ登録、ユーザ認証をまるっと任せられるサービスです。
全体像
Auth0を使うときの全体像は以下のような感じです。
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() {
}
}
実行します
ここまででユーザ登録、ユーザ認証の実装が終わったことになります。
実行してみます。
アカウントを登録していなければ、サインアップを実行します。
ログインしてみます。
ログインできました。
ほぼ、設定とチュートリアルにある実装で、ユーザ登録(サインアップ)、ユーザ認証(サインイン)を実装することができました。
ユーザ登録、認証については、セキュリティ的にしっかりとした実装が必要なのですが、差別化しにくい部分ですので、Auth0などのサービスを活用して、コア開発に集中するといいですね。