@caliatys/cognito-service
v1.5.2
Published
Typescript service for AWS Cognito with Angular
Downloads
112
Readme
Manage your users with AWS Cognito
This Angular Library, which currently supports Angular 6.x and 7.x, is a wrapper around the aws-sdk and amazon-cognito-identity-js libraries to easily manage your Cognito User Pool.
Note
The sample application uses our authentication component : @caliatys/login-form.
Important : If you plan to use the CognitoService
as we do in the sample application, please follow the next chapters (External packages installation and usage) or refer to the dedicated documentations. You can also take a look at the src/app folder to see how we use packages together in a concrete example of implementation.
Table of contents
Demo
git clone https://github.com/Caliatys/CognitoService
cd CognitoService/
npm install
Don't forget to edit the parameters located in src/app/shared/consts/cognito.const.ts.
ng build cognito-service --prod
ng serve
Installation
CognitoService
Add @caliatys/cognito-service
module as dependency to your project.
npm install @caliatys/cognito-service --save
Copy/paste src/app/shared/consts/cognito.const.ts and replace the parameters with your resource identifiers.
export const CognitoConst = {
storagePrefix : 'AngularApp',
googleId : 'XXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com',
googleScope : '',
poolData : {
UserPoolId : 'XXXXXXXXXXXXXXXXXXXXXXXXXXX', // CognitoUserPool
ClientId : 'XXXXXXXXXXXXXXXXXXXXXXXXXXX', // CognitoUserPoolClient
Paranoia : 7 // An integer between 1 - 10
},
identityPool : 'XXXXXXXXXXXXXXXXXXXXXXXXXXX', // CognitoIdentityPool
region : 'eu-west-1', // Region matching CognitoUserPool region
// Admin (optional)
adminAccessKeyId : 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
adminSecretKeyId : 'XXXXXXXXXXXXXXXXXXXXXXXXXXX'
};
Copy/paste src/app/shared/helpers/cognito.helper.ts. This file is used to simplify the implementation of the CognitoService
in your application while keeping a single instance of it.
// Angular modules
import { Injectable } from '@angular/core';
// External modules
import { CognitoService } from '@caliatys/cognito-service';
import { AuthType } from '@caliatys/cognito-service';
import { RespType } from '@caliatys/cognito-service';
// Consts
import { CognitoConst } from '../consts/cognito.const';
@Injectable()
export class CognitoHelper
{
// Services
public cognitoService : CognitoService = new CognitoService(CognitoConst);
// Consts
public cognitoConst = CognitoConst;
// Enums
public authType = AuthType;
public respType = RespType;
}
Include CognitoHelper
into the providers of app.module.ts :
...
import { CognitoHelper } from './shared/helpers/cognito.helper';
@NgModule({
...
providers :
[
CognitoHelper
...
],
...
})
export class AppModule { }
Add the API inside the <head>
of index.html to enable authentication with Google :
<script src="https://apis.google.com/js/platform.js"></script>
Add the Google type declaration :
- Add
"types": ["node", "gapi", "gapi.auth2"]
to the tsconfig.app.json file that the angular-cli creates in thesrc
directory.
External packages
LoginComponent
Install @caliatys/login-form
:
npm install @caliatys/login-form --save
Create a new login module with its routing and component :
ng generate module login --routing --no-spec
ng generate component login --no-spec
Include LoginFormModule
into login.module.ts or in module where you will use it.
...
import { LoginFormModule } from '@caliatys/login-form';
@NgModule({
...
imports :
[
LoginFormModule
...
],
...
})
export class LoginModule { }
or include LoginFormModule
into your shared.module.ts. This could be usefull if your project has nested Modules.
Angular Sharing Modules - Official documentation
You can generate it with :
ng generate module shared --no-spec
// shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { LoginFormModule } from '@caliatys/login-form';
...
@NgModule({
imports: [
CommonModule,
LoginFormModule,
...
],
exports: [
CommonModule,
LoginFormModule,
...
],
...
})
export class SharedModule {
}
// app.module.ts
...
import { SharedModule } from './shared/shared.module';
@NgModule({
...
imports :
[
SharedModule
...
],
...
})
export class AppModule { }
Create an home module with its routing and component :
ng generate module home --routing --no-spec
ng generate component home --no-spec
Bonus : Create and custom a 404 page.
ng generate module static --routing --no-spec
ng generate component static/not-found --no-spec
Include StaticModule
into app.module.ts :
...
import { StaticModule } from './static/static.module';
@NgModule({
...
imports :
[
StaticModule
...
],
...
})
export class AppModule { }
Include CognitoHelper
into not-found.component.ts :
// Angular modules
import { Component } from '@angular/core';
// Helpers
import { CognitoHelper } from '../../shared/helpers/cognito.helper';
@Component({
selector : 'app-not-found',
templateUrl : './not-found.component.html',
styleUrls : ['./not-found.component.scss']
})
export class NotFoundComponent
{
constructor(public cognitoHelper : CognitoHelper) { }
}
And use it into not-found.component.html :
<h1>404 - Page not found</h1>
<!-- Authenticated user -->
<button type="button" [routerLink]="['/home']"
*ngIf="cognitoHelper.cognitoService.isAuthenticated()">
Go to home page
</button>
<!-- Unknown user -->
<button type="button" [routerLink]="['/login']"
*ngIf="!cognitoHelper.cognitoService.isAuthenticated()">
Go to login page
</button>
To restrict the access to the home page and redirect to the login page, the routing system requires an auth-guard.helper.ts :
// Angular modules
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Route } from '@angular/router';
import { CanLoad } from '@angular/router';
// Helpers
import { CognitoHelper } from '../../shared/helpers/cognito.helper';
@Injectable()
export class AuthGuardHelper implements CanLoad
{
constructor(private router : Router, private cognitoHelper : CognitoHelper) { }
public canLoad(route : Route) : boolean
{
return this.isAuthenticated();
}
private isAuthenticated() : boolean
{
let isAuthenticated : boolean = false;
isAuthenticated = this.cognitoHelper.cognitoService.isAuthenticated();
if (!isAuthenticated)
this.router.navigate(['/login']);
return isAuthenticated;
}
}
If you don't have app-routing.module.ts, here's how to create and import it :
ng generate module app-routing --flat --module=app --no-spec
--flat puts the file in src/app instead of its own folder.
--module=app tells the CLI to register it in the imports array of the AppModule.
Angular Routing - Official documentation
Include AppRoutingModule
into app.module.ts :
...
import { AppRoutingModule } from './app-routing.module';
@NgModule({
...
imports :
[
AppRoutingModule
...
],
...
})
export class AppModule { }
Now let's add and protect the routes into app-routing.module.ts :
// Angular modules
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { Routes } from '@angular/router';
// Components
import { NotFoundComponent } from './static/not-found/not-found.component';
// Helpers
import { AuthGuardHelper } from './shared/helpers/auth-guard.helper';
const routes : Routes = [
{
path : 'login',
loadChildren : './login/login.module#LoginModule',
},
{
path : 'home',
loadChildren : './home/home.module#HomeModule',
canLoad : [ AuthGuardHelper ]
},
{ path : '', redirectTo : '/login', pathMatch : 'full' },
{ path : '**', component : NotFoundComponent }
];
@NgModule({
imports : [ RouterModule.forRoot(routes) ],
exports : [ RouterModule ],
providers : [ AuthGuardHelper ]
})
export class AppRoutingModule { }
Here is an overview of the file structure :
app/
│
├── home/
│ ├── home-routing.module.ts
│ ├── home.component.html
│ ├── home.component.scss
│ ├── home.component.ts
│ └── home.module.ts
│
├── login/
│ ├── login-routing.module.ts
│ ├── login.component.html
│ ├── login.component.scss
│ ├── login.component.ts
│ └── login.module.ts
│
├── shared/
│ ├── consts/
│ │ └── cognito.const.ts
│ ├── helpers/
│ │ ├── auth-guard.helper.ts
│ │ └── cognito.helper.ts
│ └── shared.module.ts
│
├── static/
│ ├── not-found/
│ │ ├── not-found.component.html
│ │ ├── not-found.component.scss
│ │ └── not-found.component.ts
│ ├── static-routing.module.ts
│ └── static.module.ts
│
├── app-routing.module.ts
├── app.component.html
├── app.component.scss
├── app.component.ts
└── app.module.ts
Usage
CognitoService
To start using the service, import the CognitoHelper
into the app.component.ts, for example :
// Angular modules
import { Component } from '@angular/core';
import { OnInit } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
// External modules
import { Subscription } from 'rxjs';
// Helpers
import { CognitoHelper } from './shared/helpers/cognito.helper';
@Component({
selector : 'app-root',
templateUrl : './app.component.html',
styleUrls : ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy
{
public isAuthenticated : boolean = false;
// Subscriptions
private signInSub : Subscription;
private signOutSub : Subscription;
constructor
(
private cognitoHelper : CognitoHelper,
private router : Router
) { }
public ngOnInit() : void
{
this.isAuthenticated = this.cognitoHelper.cognitoService.isAuthenticated();
if (this.isAuthenticated)
{
this.cognitoHelper.cognitoService.updateCredentials();
this.cognitoHelper.cognitoService.autoRefreshSession();
}
this.signInSub = this.signInSubscription();
this.signOutSub = this.signOutSubscription();
}
public ngOnDestroy() : void
{
this.signInSub.unsubscribe();
this.signOutSub.unsubscribe();
}
// Subscription
private signInSubscription() : Subscription
{
let signInSub : Subscription = null;
signInSub = this.cognitoHelper.cognitoService.onSignIn.subscribe(() =>
{
this.isAuthenticated = true;
});
return signInSub;
}
private signOutSubscription() : Subscription
{
let signOutSub : Subscription = null;
signOutSub = this.cognitoHelper.cognitoService.onSignOut.subscribe(() =>
{
this.isAuthenticated = false;
this.router.navigate([ '/login' ]);
});
return signOutSub;
}
}
External packages
LoginComponent
Once the LoginFormModule
is imported, you can start using the cal-login-form
component into login.component.html :
<cal-login-form #loginForm
(initialized)="initialized()"
(signUp)="signUp()"
(login)="login($event)"
(loginSocial)="loginSocial($event)"
(forgotPwd)="forgotPassword($event)"
(sendFirstPwd)="firstPassword($event)"
(sendResetPwd)="resetPassword($event)"
(saveMfaKey)="saveMfaKey($event)"
(sendMfaCode)="sendMfaCode($event)"
(stepUsr)="stepUsr($event)"
(stepPwd)="stepPwd($event)">
</cal-login-form>
The component accepts several inputs that are listed in the documentation.
Here is how we integrate the output events with LoginFormComponent
in login.component.ts :
// Angular modules
import { Component } from '@angular/core';
import { ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { MatSnackBar } from '@angular/material';
// External modules
import { LoginFormComponent } from '@caliatys/login-form';
// Helpers
import { CognitoHelper } from '../shared/helpers/cognito.helper';
@Component({
moduleId : module.id,
templateUrl : 'login.component.html',
styleUrls : ['login.component.scss']
})
export class LoginComponent
{
// @caliatys/login-form
@ViewChild('loginForm') loginForm : LoginFormComponent;
constructor
(
public router : Router,
public snackBar : MatSnackBar,
private cognitoHelper : CognitoHelper
)
{
if (this.cognitoHelper.cognitoService.isAuthenticated())
this.successfulConnection();
}
// Actions :
// Google login
public loginSocial($event : any) : void
{
let social : string = null;
social = $event.social;
if (social !== this.cognitoHelper.authType.GOOGLE)
return;
this.cognitoHelper.cognitoService.signIn(this.cognitoHelper.authType.GOOGLE).then(res =>
{
this.successfulConnection();
}).catch(err =>
{
console.error(err);
});
}
// Cognito login
public login($event : any) : void
{
let username : string = null;
let password : string = null;
username = $event.username;
password = $event.password;
this.cognitoHelper.cognitoService.signIn(this.cognitoHelper.authType.COGNITO, username, password).then(res =>
{
// Successful signIn
if (res.type === this.cognitoHelper.respType.ON_SUCCESS)
this.successfulConnection();
// First connection
if (res.type === this.cognitoHelper.respType.NEW_PASSWORD_REQUIRED)
this.loginForm.showPwdForm(true);
// MFA required
if (res.type === this.cognitoHelper.respType.MFA_REQUIRED)
this.loginForm.showMfaForm();
// MFA setup : associate secret code
if (res.type === this.cognitoHelper.respType.MFA_SETUP_ASSOCIATE_SECRETE_CODE)
this.loginForm.showMfaSetupForm('JBSWY3DPEHPK3PXP', 'otpauth://totp/[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Caliatys');
}).catch(err =>
{
// ON_FAILURE / MFA_SETUP_ON_FAILURE
console.error('LoginComponent : login -> signIn', err);
this.snackBar.open(err.data.message, 'X');
});
}
// First connection
public firstPassword($event : any) : void
{
let username : string = null;
let newPassword : string = null;
username = $event.username;
newPassword = $event.password;
this.cognitoHelper.cognitoService.newPasswordRequired(newPassword).then(res =>
{
// Success
if (res.type === this.cognitoHelper.respType.ON_SUCCESS)
this.loginForm.hidePwdForm();
// MFA required
if (res.type === this.cognitoHelper.respType.MFA_REQUIRED)
this.loginForm.showMfaForm();
}).catch(err =>
{
console.error('LoginComponent : firstPassword -> changePassword', err);
this.snackBar.open(err.data.message, 'X');
});
}
// Forgot password
public forgotPassword($event : any) : void
{
let username : string = null;
username = $event.username;
if (!username)
return; // Username required
this.cognitoHelper.cognitoService.forgotPassword(username).then(res =>
{
// Verification code
if (res.type === this.cognitoHelper.respType.INPUT_VERIFICATION_CODE)
this.loginForm.showPwdForm(false);
}).catch(err =>
{
console.error('LoginComponent : forgotPassword -> forgotPassword', err);
this.snackBar.open(err.data.message, 'X');
});
}
// Reset password : complete the forgot password flow
public resetPassword($event : any) : void
{
let newPassword : string = null;
let verifCode : string = null;
newPassword = $event.password;
verifCode = $event.verificationCode;
this.cognitoHelper.cognitoService.confirmPassword(newPassword, verifCode).then(res =>
{
this.loginForm.hidePwdForm(newPassword); // Password updated successfully
}).catch(err =>
{
console.error('LoginComponent : resetPassword -> confirmPassword', err);
this.snackBar.open(err.data.message, 'X');
});
}
private successfulConnection() : void
{
this.router.navigate(['/home']);
}
Variables
Events that you can subscribe to deal with signIn
and signOut
events
// Events that you can subscribe to
public onSignIn : EventEmitter<null>;
public onSignOut : EventEmitter<null>;
Methods
Registration
SignUp
Register a new user :
this.cognitoHelper.cognitoService.signUp('username', 'password').then(res => {
let signUpResult : AWSCognito.ISignUpResult = res.data;
}).catch(err => { });
Confirm registration
Depending on your settings, email confirmation may be required. In that case, the following function must be called :
this.cognitoHelper.cognitoService.confirmRegistration()
.then(res => { }).catch(err => { });
Resend confirmation code
this.cognitoHelper.cognitoService.resendConfirmationCode();
SignIn
Connect an existing user with Google or Cognito.
this.cognitoHelper.cognitoService.signIn(this.cognitoHelper.authType.GOOGLE).then(res => {
let profile : gapi.auth2.BasicProfile = res.data;
let email = profile.getEmail();
}).catch(err => { });
Cognito
this.cognitoHelper.cognitoService.signIn(this.cognitoHelper.authType.COGNITO, 'username', 'password').then(res => {
// Successful connection
if (res.type === RespType.ON_SUCCESS)
let session : AWSCognito.CognitoUserSession = res.data;
// First connection
if (res.type === RespType.NEW_PASSWORD_REQUIRED)
// MFA required
if (res.type === RespType.MFA_REQUIRED)
// MFA setup : associate secret code
if (res.type === RespType.MFA_SETUP_ASSOCIATE_SECRETE_CODE)
let secretCode : string = res.data;
}).catch(err => {
// Error
if (err.type === RespType.ON_FAILURE)
let err : any = res.data;
// MFA setup : error
if (err.type === RespType.MFA_SETUP_ON_FAILURE)
let err : any = res.data;
});
Refresh session
Generate new refreshToken
, idToken
and accessToken
with a new expiry date.
If successful, you retrieve 3 auth tokens and the associated expiration dates (same as signIn
).
this.cognitoHelper.cognitoService.refreshCognitoSession().then(res => {
let session : AWSCognito.CognitoUserSession = res.data;
}).catch(err => { });
SignOut
this.cognitoHelper.cognitoService.signOut();
MFA
Send MFA code
Complete the MFA_REQUIRED
sent by the signIn
or by the newPasswordRequired
method using the mfaCode received by SMS to finish the signIn flow.
this.cognitoHelper.cognitoService.sendMFACode('mfaCode', 'SOFTWARE_TOKEN_MFA or SMS_MFA').then(res => {
let session : AWSCognito.CognitoUserSession = res.data;
}).catch(err => { });
Get MFA status
If MFA is enabled for this user, retrieve its options. Otherwise, returns null.
this.cognitoHelper.cognitoService.getMFAOptions().then(res => {
let mfaOptions : AWSCognito.MFAOption[] = res.data;
}).catch(err => { });
Enable or Disable MFA
let enableMfa : boolean = true;
this.cognitoHelper.cognitoService.setMfa(enableMfa)
.then(res => { }).catch(err => { });
Password
New password required
Complete the NEW_PASSWORD_REQUIRED
response sent by the signIn
method to finish the first connection flow.
this.cognitoHelper.cognitoService.newPasswordRequired('newPassword').then(res => {
// Success
if (res.type === RespType.ON_SUCCESS)
// ...
// MFA required
if (res.type === RespType.MFA_REQUIRED)
// ...
}).catch(err => { });
Forgot password
Start a forgot password flow.
Cognito will send a verificationCode
to one of the user's confirmed contact methods (email or SMS) to be used in the confirmPassword
method below.
this.cognitoHelper.cognitoService.forgotPassword('username').then(res => {
// Verification code
if (res.type === RespType.INPUT_VERIFICATION_CODE)
// ...
}).catch(err => { });
Confirm password
Complete the INPUT_VERIFICATION_CODE
response sent by the forgotPassword
method to finish the forgot password flow.
this.cognitoHelper.cognitoService.confirmPassword('newPassword', 'verificationCode')
.then(res => { }).catch(err => { });
Change password
Use this method to change the user's password.
this.cognitoHelper.cognitoService.changePassword('oldPassword', 'newPassword')
.then(res => { }).catch(err => { });
Helpers
Is authenticated
Compare the token expiration date with the current date.
let connected : boolean = this.cognitoHelper.cognitoService.isAuthenticated();
Get username
let username : string = this.cognitoHelper.cognitoService.getUsername();
Get provider
let provider : string = this.cognitoHelper.cognitoService.getProvider();
Get id token
let idToken : string = this.cognitoHelper.cognitoService.getIdToken();
Get tokens
let tokens : any = this.cognitoHelper.cognitoService.getTokens();
// tokens = {
// accessToken : string,
// accessTokenExpiresAt : number, (milliseconds)
// idToken : string,
// idTokenExpiresAt : number, (milliseconds)
// refreshToken : string
// }
Automatic refresh session
this.cognitoHelper.cognitoService.autoRefreshSession();
Get token expiration date
let expiresAt : Date = this.cognitoHelper.cognitoService.getExpiresAt();
Get the remaining time
let remaining : Number = this.cognitoHelper.cognitoService.getRemaining(); // milliseconds
Initialize credentials
this.cognitoHelper.cognitoService.initCredentials();
Update credentials
this.cognitoHelper.cognitoService.updateCredentials();
Get credentials
this.cognitoHelper.cognitoService.getCredentials()
.then(res => { }).catch(err => { });
STS - getCallerIdentity
this.cognitoHelper.cognitoService.sts()
.then(res => { }).catch(err => { });
Admin
Admin create user
this.cognitoHelper.cognitoService.adminCreateUser('username', 'password')
.then(res => { }).catch(err => { });
Admin delete user
this.cognitoHelper.cognitoService.adminDeleteUser('username')
.then(res => { }).catch(err => { });
Admin reset user password
this.cognitoHelper.cognitoService.adminResetUserPassword('username')
.then(res => { }).catch(err => { });
Admin update user attributes
let userAttributes : AWS.CognitoIdentityServiceProvider.Types.AttributeListType;
this.cognitoHelper.cognitoService.adminUpdateUserAttributes('username', userAttributes)
.then(res => { }).catch(err => { });
Admin helpers
Reset expired account
this.cognitoHelper.cognitoService.resetExpiredAccount('usernameKey', 'username')
.then(res => { }).catch(err => { });
Set admin
this.cognitoHelper.cognitoService.setAdmin();
Dependencies
Important : This project uses the following dependencies :
"peerDependencies" : {
"@angular/common" : "^6.0.0 || ^7.0.0",
"@angular/core" : "^6.0.0 || ^7.0.0",
"rxjs" : "^6.0.0",
"rxjs-compat" : "^6.0.0",
"amazon-cognito-identity-js" : "^2.0.6",
"aws-sdk" : "^2.247.1",
"@types/gapi" : "0.0.35",
"@types/gapi.auth2" : "0.0.47"
},
"devDependencies" : {
"@types/node" : "10.12.0"
}
If it's an empty Angular application :
- Add
"types": ["node"]
to the tsconfig.app.json file that the angular-cli creates in thesrc
directory. - Add
(window as any).global = window;
to the polyfills.ts file, as mentioned here : angular/angular-cli#9827 (comment)
Roadmap
In progress
Planning
Contributions
Contributions are welcome, please open an issue and preferably submit a pull request.
Development
CognitoService is built with Angular CLI.