How I implemented the authentication with Amplify in Angular
Tags: angular, amplify, aws, authentication
Published: 2022-10-20 09:25:13

Authentication with Amplify is very easy even though I struggled a little bit at the beginning.
Here's how I implemented my authentication. Let me know what you think.

- I created my auth auth.service.ts with all the possible operations:

`
import { Injectable } from '@angular/core';
import { CognitoUser } from 'amazon-cognito-identity-js';
import { Auth } from 'aws-amplify';
import { CognitoHostedUIIdentityProvider } from '@aws-amplify/auth';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  user: CognitoUser | undefined;
  constructor() {}

  async signIn({
    email,
    psw,
  }: {
    email: string;
    psw: string;
  }): Promise<CognitoUser> {
    return Auth.signIn(email, psw);
  }

  async setNewPasswordFirstLogin({
    psw,
  }: {
    psw: string;
  }): Promise<CognitoUser | any> {
    return Auth.completeNewPassword(this.user, psw);
  }

  async currentAuthenticatedUser(): Promise<CognitoUser> {
    return Auth.currentAuthenticatedUser();
  }

  async signOut(): Promise<any> {
    return Auth.signOut();
  }

  async forgotPasswordSendEmail({
    email,
  }: {
    email: string;
  }): Promise<CognitoUser> {
    return Auth.forgotPassword(email);
  }

  async forgotPasswordSetNewPsw({
    email,
    psw,
    code,
  }: {
    email: string;
    psw: string;
    code: string;
  }): Promise<string> {
    return Auth.forgotPasswordSubmit(email, code, psw);
  }

  setUser(user: CognitoUser) {
    this.user = user;
  }

  async federatedSignIn({ type }: { type: string }) {
    let provider: CognitoHostedUIIdentityProvider | undefined = undefined;
    if (type === 'Google') provider = CognitoHostedUIIdentityProvider.Google;
    if (type === 'Apple') provider = CognitoHostedUIIdentityProvider.Apple;
    if (type === 'Facebook')
      provider = CognitoHostedUIIdentityProvider.Facebook;
    if (provider) {
      return Auth.federatedSignIn({
        provider: provider,
      });
    }
    return;
  }
}
`

- I created the jwt.interceptor.ts, this was the toughest part that I implemented.

```
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
} from '@angular/common/http';
import { Observable, from, switchMap, catchError, throwError } from 'rxjs';
import { environment } from 'src/environments/environment';
import { AuthService } from 'src/app/core/services/auth.service';
import { Auth } from 'aws-amplify';
import { CognitoUserSession } from 'amazon-cognito-identity-js';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {
  constructor(private authService: AuthService) {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return from(Auth.currentSession()).pipe(
      switchMap((auth: any) => {
        // switchMap() is used instead of map().
        const jwt: string = auth.idToken.jwtToken;
        const authRequest = request.clone({
          setHeaders: {
            Authorization: `Bearer ${jwt}`,
          },
        });
        return next.handle(authRequest);
      }),
      catchError((err) => {
        if (err.status) {
          return throwError(() => new Error(err));
        }
      })
    );
  }
}

```

- I implemented the error.interceptor.ts as well for the auto logout

```
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { LoaderService } from 'src/app/shared/services/loader.service';

/**
 * This is used to logout the user, when the server responds with an unathorized status code.
 * Especially when the session token expires.
 * @export
 * @class ErrorInterceptor
 * @implements {HttpInterceptor}
 */
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(
    private readonly router: Router,
    private readonly authService: AuthService,
  ) {}

  /**
   * Interceptor intercepts the responses, and then process based on the received status code
   * @param {HttpRequest<any>} request
   * @param {HttpHandler} next
   * @returns {Observable<HttpEvent<any>>}
   * @memberof ErrorInterceptor
   */
  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((err) => {
        if (err.status === 401) {
          // auto logout if 401 response returned from api
          this.authService
            .signOut()
            .then(() => {
              this.router.navigate(['/auth']);
            })
            .catch((err) => console.log(err));
        }

        // err.error is not null, if the ResponsenEntity contains an Exception
        // err.error.message will give the custom message send from the server
        const error = err.error.errorMessage || err.statusText;
        return throwError(() => new Error(error));
      })
    );
  }
}

```

I created a login component but it is a simple component that calls the service.

Let me know what you think about this implementation and if there's a better way to do this particular task.