ぺーぺーSEのテックブログ

備忘録用メモサイト

RSSを購読する

Cognitoを使ってユーザ認証して画面遷移するAngularアプリ

以下の記事で紹介したCognitoで認証するAngularアプリにAngular Routerで画面遷移ロジックを加えたアプリを作成する。

@angular/router を使うことでAngularで画面遷移を実現できる。

アプリケーションの仕様

  • /login/menu がある
  • /login 画面では、Amazon Cognito User Pool を使ってSignUp、SignInが可能
  • /login 画面でSignInすると /menu 画面へ遷移する
  • /menu 画面では、Session StorageにSignInユーザ情報が無い場合、 /login 画面へ遷移する

アプリケーションの作成手順

以下のコマンドでプロジェクトおよび必要なファイルを作成する。

1
2
3
4
5
6
7
$ ng new cognito-js --style=scss
$ cd cognito-js
$ npm install amazon-cognito-identity-js --save
$ ng generate class app.routing
$ ng generate service services/cognito
$ ng generate component components/login
$ ng generate component components/menu

以下の順でソースを編集する。

  • src/tsconfig.app.json
  • src/app/app.routing.ts
    • 遷移する画面(Component)情報をここへ集約する
  • src/app/app.module.ts
  • src/environments/environment.ts
  • src/app/app.component.ts
  • src/app/services/cognito.service.ts
  • src/app/components/login/login.component.ts
    • /login 画面のComponent
  • src/app/components/menu/menu.component.ts
    • /menu 画面のComponent
  • src/app/components/login/login.component.html
    • /login 画面
  • src/app/components/menu/menu.component.html
    • /menu 画面
  • src/app/app.component.html

src/tsconfig.app.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "es2015",
"baseUrl": "",
"types": ["node"] // ここを追加
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

src/app/app.routing.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ModuleWithProviders } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './components/login/login.component';
import { MenuComponent } from './components/menu/menu.component';
const appRoutes: Routes = [
{
path: 'login',
component: LoginComponent
},
{
path: 'menu',
component: MenuComponent
},
{
path: '',
redirectTo: '/login',
pathMatch: 'full'
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);

{ path: '', redirectTo: '/login', pathMatch: 'full' } の記述でルートパスへアクセスされると、 /login 画面へリダイレクトされる。

src/app/app.module.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { AppComponent } from './app.component';
import { LoginComponent } from './components/login/login.component';
import { MenuComponent } from './components/menu/menu.component';
import { routing } from './app.routing'; // 追記
@NgModule({
declarations: [
AppComponent,
LoginComponent,
MenuComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
routing // 追記
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

src/environments/environment.ts

1
2
3
4
5
6
export const environment = {
production: false,
region: 'ap-northeast-1',
userPoolId: 'ap-northeast-1_xxxxxxxxx',
clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
};

src/app/app.component.ts

1
2
3
4
5
6
7
8
9
10
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'Cognito Sample'; // ここを好きなタイトルに編集
}

src/app/services/cognito.service.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { Injectable } from '@angular/core';
import * as AWS from "aws-sdk";
import { CognitoUserPool, CognitoUserAttribute, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
import { environment } from '../../environments/environment';
@Injectable()
export class CognitoService {
userPool = null;
constructor() {
AWS.config.region = environment.region;
const data = { UserPoolId: environment.userPoolId, ClientId: environment.clientId};
this.userPool = new CognitoUserPool(data);
}
signUp(username, password, email, phone) {
const userData = {
Username : username,
Pool : this.userPool,
Storage: sessionStorage
};
let attributeList = [];
const dataEmail = {
Name : 'email',
Value : email
};
const dataPhoneNumber = {
Name : 'phone_number',
Value : phone
};
let attributeEmail = new CognitoUserAttribute(dataEmail);
let attributePhoneNumber = new CognitoUserAttribute(dataPhoneNumber);
attributeList.push(attributeEmail);
attributeList.push(attributePhoneNumber);
this.userPool.signUp(username, password, attributeList, null, function(err, result){
if (err) {
alert(err);
return;
}
const cognitoUser = result.user;
alert("SignUp is success!\nUser name is " + cognitoUser.getUsername() + ".\nYou need to check your SMS or E-Mail.");
});
return;
}
confirmRegistration(username, verification_code) {
const userData = {
Username : username,
Pool : this.userPool,
Storage: sessionStorage
};
const cognitoUser = new CognitoUser(userData);
cognitoUser.confirmRegistration(verification_code, true, function(err, result) {
if (err) {
alert(err);
return;
}
alert('Registration is success!');
console.log('call result: ' + result);
});
return;
}
signIn(username, password, callback) {
const userData = {
Username : username,
Pool : this.userPool,
Storage: sessionStorage
};
const cognitoUser = new CognitoUser(userData);
const authenticationData = {
Username : username,
Password : password
};
const authenticationDetails = new AuthenticationDetails(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
alert('SignIn is success!');
console.log('access token + ' + result.getAccessToken().getJwtToken());
if(callback) callback();
},
onFailure: function(err) {
alert(err);
// UserNotConfirmedException: User is not confirmed.
}
});
return;
}
getSignInUserNmae() {
let username_key = 'CognitoIdentityServiceProvider.' + environment.clientId + '.LastAuthUser';
return sessionStorage.getItem(username_key);
}
signOut(callback) {
sessionStorage.clear();
if(callback) callback();
}
}

src/app/components/login/login.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import { Component, OnInit } from '@angular/core';
import { CognitoService } from '../../services/cognito.service';
import { Router } from '@angular/router';
import { routing } from '../../app.routing';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss'],
providers: [CognitoService]
})
export class LoginComponent implements OnInit {
username = '';
password = ``;
email = ``;
phone = ``;
verification_code = ``;
constructor(
private cognitoService: CognitoService,
private router: Router
) { }
ngOnInit() {
}
siginUp() {
this.cognitoService.signUp(this.username, this.password, this.email, this.phone);
}
confirmRegistration() {
this.cognitoService.confirmRegistration(this.username, this.verification_code);
}
signIn() {
// Session Storageにセッション情報が格納されるまで待ってから画面遷移
let gotoMenu = function (router: Router, cognitoService: CognitoService) {
let timerID = setInterval(function(){
if(cognitoService.getSignInUserNmae()){
//wait終了時の後処理
router.navigate(['/menu']);
clearInterval(timerID);
timerID = null;
}
}, 100);
}
this.cognitoService.signIn(this.username, this.password, gotoMenu(this.router, this.cognitoService));
}
}

signIn() メソッドでは cognitoUser.authenticateUsercallback.onSuccess の段階で Session Storage にセッション情報が格納されていなかったので、 setInterval でセッションが格納されるまで wait するロジックを追加してみた。

src/app/components/menu/menu.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { Component, OnInit } from '@angular/core';
import { CognitoService } from '../../services/cognito.service';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { environment } from '../../../environments/environment';
import { Router } from '@angular/router';
import { routing } from '../../app.routing';
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.scss'],
providers: [CognitoService]
})
export class MenuComponent implements OnInit {
url = '';
result = '';
gotoLogin = function (router: Router) {
router.navigate(['/login']);
}
constructor(
private http: Http,
private cognitoService: CognitoService,
private router: Router
) { }
ngOnInit() {
if(!this.cognitoService.getSignInUserNmae()) {
this.cognitoService.signOut(this.gotoLogin(this.router));
}
}
deleteSession() {
this.cognitoService.signOut(this.gotoLogin(this.router));
}
get() {
let username_key = 'CognitoIdentityServiceProvider.' + environment.clientId + '.LastAuthUser';
let username = sessionStorage.getItem(username_key);
let idToken_key = 'CognitoIdentityServiceProvider.' + environment.clientId + '.' + username + '.idToken';
let idToken = sessionStorage.getItem(idToken_key);
let headers = new Headers({ 'Authorization': idToken });
let options = new RequestOptions({ headers: headers });
return this.http.get(this.url, options)
.map(res => res.text())
.subscribe(t => this.result = t);
}
}

src/app/components/login/login.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<div>
<h2>サインアップ</h2>
<p>以下の項目を入力してユーザを新規に仮登録します。</p>
<table>
<tr><td>ユーザ名:</td><td><input type="text" [(ngModel)]="username"></td><tr>
<tr><td>パスワード:</td><td><input type="text" [(ngModel)]="password"></td></tr>
<tr><td>メールアドレス:</td><td><input type="text" [(ngModel)]="email"></td></tr>
<tr><td>携帯番号:</td><td><input type="text" [(ngModel)]="phone"></td></tr>
</table>
<button (click)="siginUp()">サインアップ</button>
</div>
<hr />
<div>
<h2>認証コードの確認</h2>
<p>サインアップしたユーザにメールやSMSで送付された認証コードを確認し、ユーザを本登録します。</p>
<p>認証コードは6桁数字です。</p>
<table>
<tr><td>ユーザ名:</td><td>{{username}}</td></tr>
<tr><td>認証コード:</td><td><input type="text" [(ngModel)]="verification_code"></td></tr>
</table>
<button (click)="confirmRegistration()">確認</button>
</div>
<hr />
<div>
<h2>サインイン</h2>
<p>本登録したユーザでサインイン(ログイン)します。</p>
<table>
<tr><td>ユーザ名:</td><td>{{username}}</td></tr>
<tr><td>パスワード:</td><td>{{password}}</td></tr>
</table>
<button (click)="signIn()">サインイン</button>
</div>

src/app/components/menu/menu.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
<a routerLink="/login" (click)="deleteSession()">ログアウト</a>
</div>
<div>
<h2>GET アクセス</h2>
<p>Session StorageのidTokenをAuthorizationヘッダへ付与してGetリクエストを送付します。</p>
<p>API GatewayのAuthorizationなどで試してみてください。</p>
<table>
<tr><td>URL: </td><td><input type="text" [(ngModel)]="url"></td></tr>
</table>
<button (click)="get()">Send</button>
<table>
<tr><td>{{result}}</td></tr>
</table>
</div>

src/app/app.component.html

1
2
3
4
5
<h1>
{{title}}
</h1>
<hr />
<router-outlet></router-outlet>

<router-outlet> の箇所が Router によって画面が切り替えられる。

参考