# Access Control with AWS Cognito
Cognito is an services provided by Amazon for managing user profiles, tracking users sessions and, when combined with IAM roles, securing AWS resources.The Cloudbash application uses AWS Cognito for the following functionality:
- User Authentication (Sign up & Sign in)
- Authorization of
- Role based Authorization
- Identity Verification (by SMS)
- User Account Recovery (by email)
# Serverless Framework
Cognito User Pool - source
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: Cloudbash
User Pool Client - source
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: CloudbashWebApp
GenerateSecret: false
UserPoolId:
Ref: CloudbashCognitoUserPool
# Client-side
# Implementing user sign up and login with AWS- Cognito and Amplify
AWS Amplify is an Javascript library that provides tools for developing serverless web applications using amazon web services. Amplify has many features that will speed up your development. For this application we are using the Authentication API and Angular UI components for the user sign up and login flow.
The preferred way for using Cognito with AWS Amplify is to generate the user pool with the Amplify CLI. But for this project we will be using a user pool that we have created with the Serverless Framework. The ID and region of the user pool need to be provided in the aws-exports.js file in the source folder of your project.
Dependencies
app.module.ts - source
The AmplifyService is registered as an provider.
import { AmplifyService, AmplifyModules } from 'aws-amplify-angular';
import Auth from '@aws-amplify/auth';
providers: [
{
provide: AmplifyService,
useFactory: () => {
return AmplifyModules({
Auth
});
}
}
],
authentication.service.ts - source
The authentication service contains logic that will listen to auth changes from the amplify service. When a user is succesfully logged in, the user information is stored for use in the application.
The getRole() function provides a fast way for retrieving the role from the signed in user.
import { Observable, BehaviorSubject } from 'rxjs';
import { Injectable } from '@angular/core';
import { AmplifyService } from 'aws-amplify-angular';
import { Auth } from 'aws-amplify';
import { fromPromise } from 'rxjs/observable/fromPromise';
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { Router } from '@angular/router';
import { Role } from '../models/user.model';
@Injectable({
providedIn: 'root',
})
export class AuthenticationService {
public loggedIn: BehaviorSubject<boolean>;
public user: any;
public currentUserRole: Role;
signedIn: boolean;
constructor(private amplifyService: AmplifyService,
private router: Router) {
this.loggedIn = new BehaviorSubject<boolean>(false);
this.amplifyService.authStateChange$
.subscribe(authState => {
if (!authState.user) {
this.user = null;
} else {
this.user = authState.user;
this.currentUserRole = this.getRole(authState.user);
}
if(authState.state === 'signedIn'){
this.loggedIn.next(true);
this.signedIn = true;
}
if(authState.state === 'signedOut'){
this.loggedIn.next(false);
this.signedIn = false;
}
});
}
public isAuthenticated(): Observable<boolean> {
return fromPromise(Auth.currentAuthenticatedUser())
.pipe(
map(result => {
this.loggedIn.next(true);
return true;
}),
catchError(error => {
this.loggedIn.next(false);
return of(false);
})
);
}
public signOut() {
this.loggedIn.next(false);
this.signedIn = false;
fromPromise(Auth.signOut())
.subscribe(
result => {
this.router.navigate(['/sign-in']);
},
);
}
public isAdmin() : boolean {
return this.currentUserRole === Role.Admin;
}
public getRole(user: any): Role {
const roles: any[] = user.signInUserSession
.idToken.payload['cognito:groups'];
if (roles) {
if (roles[0] == 'Admin') {
return Role.Admin;
} else {
return Role.User;
}
}
}
}
auth-page.component.ts - source
The Amplify Authenticator component implements forms for user sign up, log in and account recovery.
<amplify-authenticator></amplify-authenticator>
auth.guard.ts - source
The AutGuard can be used to authorize users when they visit a specific page. When the user is not authenticated, or does not have the correct role, it will be redirected to the log-in page.
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthenticationService } from '../services/authentication.service';
import { tap } from 'rxjs/internal/operators/tap';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private auth: AuthenticationService
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.auth.isAuthenticated()
.pipe(
tap(loggedIn => {
if (!loggedIn) {
this.router.navigate(['/sign-in']);
} else {
const role = this.auth.currentUserRole;
if (route.data.roles && route.data.roles.indexOf(role) === -1) {
// role not authorised so redirect to home page
this.router.navigate(['/sign-in']);
return false;
}
// authorised so return true
return true;
}
})
);
}
}
jwt.interceptor.ts - source
The JWT Interceptor automatically adds an Authorization header with the JWT Token when a HTTP call is made.
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { AuthenticationService } from '../services/authentication.service';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthenticationService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let currentUser;
if (this.authenticationService.user) {
currentUser = this.authenticationService.user.signInUserSession.idToken.jwtToken;
}
if (currentUser) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser}`
}
});
}
return next.handle(request);
}
}
app-routing.module.ts - source
Making use of the AuthGuard to secure routes. The example below makes sure that only authenticated users with the admin rule can visit the page on the /admin path.
{
path: '/admin',
component: AdminDashboardPageComponent,
canActivate: [AuthGuard],
data: { roles: [Role.Admin] }
},
# Server-side
# Serverless Framework
ApiGateWayAuthorizer.yml - source
Most simple way of authorizing REST Endpoints against a Cognito user pool.
Type: AWS::ApiGateway::Authorizer
Properties:
AuthorizerResultTtlInSeconds: 300
IdentitySource: method.request.header.Authorization
Name: cognito-authorizer
RestApiId:
Ref: "ApiGatewayRestApi"
Type: COGNITO_USER_POOLS
ProviderARNs:
- [COGNITO USER POOL ARN]
ApiGateWayAuthorizer.yml - source
Example of an Lambda (exposed as an REST Endpoint with API Gateway) secured with the ApiGateWayAuthorizer.
handler: Cloudbash.Lambda::Cloudbash.Lambda.Functions.Concerts.CreateConcertFunction::Run
events:
- http:
path: concerts
method: post
cors:
origin: '*'
headers:
- Authorization
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
Ref: apiGatewayAuthorizer