この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、クラスメソッドの岡です。
Auth0ではSPA用のSDK(@auth0/auth0-spa-js)が提供されていますが、いつの間にかAngular専用の SDK, auth0-angularがリリースされていたので早速試してみました!
ちなみにReact用のSDKもこちらの記事で紹介されています。
React SDK と同様に、内部的には auth0-spa-js
を利用していますが、さらに簡易的な記述でログインなどの認証周りの実装が可能になります。
動作確認環境
- Angular: 10.1.5
- @auth0/auth0-angular: 1.0.0
試してみる
セットアップ
まずはAngularアプリを作成してライブラリをインストールします。
$ ng new sample-auth0-angular
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? Stylus
$ ng add @auth0/auth0-angular
$ npm install --save -D @types/auth0-angular
ログイン機能の実装
今回は一番実装コストが低いユニバーサルログインの機能を使います。
$ ng g component components/pages/login
environment.ts
export const environment = {
production: false,
AUTH0_DOMAIN: 'your-tenant-name.us.auth0.com',
AUTH0_CLIENT_ID: 'tCJlygg99ZrRik5M7rVh0VFDsfFW', // Auth0上に作成したSINGLE PAGE APPLICATIONのClient ID
API_URL: 'https://example.com/v1', // APIのエンドポイント
AUDIENCE: 'https://example.com/v1', // Auth0上に作成したCUSTOM APIのaudience
};
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AuthModule } from '@auth0/auth0-angular';
import { AppRoutingModule } from './app-routing.module';
import { environment } from './../environments/environment';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/pages/login/login.component';
@NgModule({
declarations: [AppComponent, LoginComponent],
imports: [
BrowserModule,
AppRoutingModule,
AuthModule.forRoot({
domain: environment.AUTH0_DOMAIN,
clientId: environment.AUTH0_CLIENT_ID,
audience: environment.AUDIENCE,
redirectUri: `${window.location.origin}`,
}),
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
AuthModuleのインポート文を追加してAuthModule.forRoot()にAuth0のドメインと事前にAuth0上に作成したSINGLE PAGE APPLICATIONのClient IDを追加します。
login.component.html
<button (click)="login()">Log in</button>
ユニバーサルログインの画面に遷移させるためにボタンのみ設置しておきます。
login.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from '@auth0/auth0-angular';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.styl'],
})
export class LoginComponent implements OnInit {
constructor(public auth: AuthService) {}
ngOnInit(): void {}
login(): void {
// Call this to redirect the user to the login page
this.auth.loginWithRedirect();
}
}
ボタンからの動線に login()
を追加して AuthService
の loginWithRedirect
を呼び出せるようにしておきます。
これだけでログイン機能は完成です!
AuthGuardの実装
ログインができるようになったら、ログイン後の遷移先を用意します。
$ ng g component components/pages/home
ログインしたユーザーしか表示できないようroutingに AuthGuard を追加します。
app-routing.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard } from '@auth0/auth0-angular';
import { LoginComponent } from './components/pages/login/login.component';
import { HomeComponent } from './components/pages/home/home.component';
const routes: Routes = [
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
{ path: 'login', component: LoginComponent },
];
@NgModule({
declarations: [],
imports: [CommonModule, RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
home.component.html
<ul *ngIf="auth.user$ | async as user">
<li>{{ user.name }}</li>
<li>{{ user.email }}</li>
</ul>
<button *ngIf="auth.isAuthenticated$ | async" (click)="logout()">
Log out
</button>
ユーザー名の表示とログアウトボタンのみ設置しておきます。
home.component.ts
import { Component, OnInit } from '@angular/core'
import { AuthService } from '@auth0/auth0-angular'
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.styl'],
})
export class HomeComponent implements OnInit {
userInfo: {[key: string]: any} = {}
constructor(public auth: AuthService) {}
ngOnInit(): void {
this.auth.user$.subscribe(user => {
this.userInfo = user
})
}
logout(): void {
this.auth.logout({ returnTo: window.location.origin });
}
}
APIのリクエスト
APIの呼び出し時にヘッダーにアクセストークンを付与します。
これはauth0-angularの AuthHttpInterceptor
をprovidersに追加する事で自動化してくれます。
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AuthModule, AuthHttpInterceptor } from '@auth0/auth0-angular';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { environment } from './../environments/environment';
// Components
import { AppComponent } from './app.component';
import { LoginComponent } from './components/pages/login/login.component';
import { HomeComponent } from './components/pages/home/home.component';
@NgModule({
declarations: [AppComponent, LoginComponent, HomeComponent],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
AuthModule.forRoot({
domain: environment.AUTH0_DOMAIN,
clientId: environment.AUTH0_CLIENT_ID,
useRefreshTokens: true,
audience: environment.AUDIENCE,
redirectUri: `${window.location.origin}`,
httpInterceptor: {
allowedList: [
{
uri: `${environment.API_URL}/*`,
tokenOptions: {
audience: environment.AUDIENCE,
},
},
],
},
}),
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true },
],
bootstrap: [AppComponent],
})
export class AppModule {}
あとはangular標準のHttpClientModuleでAPIを実行するだけです。
簡易的に動作確認するためAPI実行のボタンを設置します。
home.component.html
<button (click)="request()">API実行</button>
<ul *ngIf="auth.user$ | async as user">
<li>{{ user.name }}</li>
<li>{{ user.email }}</li>
</ul>
<button *ngIf="auth.isAuthenticated$ | async" (click)="logout()">
Log out
</button>
HttpClientでgetリクエストを実行します。
home.component.ts
import { Component, OnInit } from '@angular/core'
import { ApiService } from 'src/app/services/api.service'
import { AuthService } from '@auth0/auth0-angular'
import { HttpClient } from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.styl'],
})
export class HomeComponent implements OnInit {
userInfo: {[key: string]: any} = {}
constructor(private api: HttpClient, public auth: AuthService) {}
ngOnInit(): void {
this.auth.user$.subscribe(user => {
this.userInfo = user
})
}
logout(): void {
// Call this to log the user out of the application
this.auth.logout({ returnTo: window.location.origin });
}
request(): void {
const url = `${environment.API_URL}/items`;
this.http.get(url).subscribe(result => console.log(result));
}
}
これで、内部的にgetTokenSilentlyを呼び出してトークンを取得し、ヘッダーに挿入まで自動的にやってくれます。非常に便利ですね!
認証フローは Authorization Code + PKCE
上記で紹介したSDKを使った場合、認証フローとしてはAuthorization Code + PKCEが用いられます。
上記のコードのみだと、OAuth2.0のどの認証フローを使うかは、ほぼ意識せずにも実装ができますが、以下の公式ドキュメントが非常にわかりやすくまとまっているので一読をお勧めします。
Auth0社筒井様のPKCEで実際にアクセストークンを取得するまでの動作を解説している以下のブログも非常にわかりやすいです。