The comprehensive step by step Angular 10 tutorial on implementing Oauth2 login and refresh token in front-end web app
In this Angular 10 tutorial, we will implement the Oauth2 login and refresh token in the front-end web app. We will use our existing Node-Express-PostgreSQL Oauth2 server as the back-end.
The scenario for this tutorial is very simple. New users register to the Angular application using username, password, and name. The registered user login to the Angular app to get an access token and refresh token. The access token and refresh token save to local storage. Every request to the secure endpoint from the secure or guarded page should contain a valid access token.
The following tools, frameworks, and modules are required for this tutorial:
We assume that you have installed the latest recommended Node.js on your computer. Let’s check them by type these commands in the terminal or Node command line.
node -v
v12.18.0
npm -v
6.14.7
That’s our Node and NPM version. Let’s get started with the main steps!
Creating the Angular 10 application, we will use Angular-CLI. The Angular CLI is a command-line interface tool that use to initialize, develop, scaffold, and maintain Angular applications directly from a terminal or cmd. To install the Angular-CLI, type this command from the terminal and cmd.
sudo npm install -g @angular/cli
Now, we can create a new Angular 10 application by type this command.
ng new angular-oauth2
If you get these questions, just choose ‘y’ and ‘SCSS’.
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS [ https://sass-lang.com/
documentation/syntax#scss ]
Next, go to the newly created Angular 10 application then check it by running the application for the first time.
cd ./angular-oauth2 && ng serve
Open the browser then go to ‘localhost:4200’ and you will this standard Angular welcome page.
We will do any token CRUD operation in service, also, REST API operation in a different service. For that, generate these services using Angular-CLI.
ng g service auth
ng g service token
Open and edit src/app/token.service.ts
then add these constant variables after the imports.
const ACCESS_TOKEN = 'access_token';
const REFRESH_TOKEN = 'refresh_token';
Add these token CRUD operation functions after the constructor function.
getToken(): string {
return localStorage.getItem(ACCESS_TOKEN);
}
getRefreshToken(): string {
return localStorage.getItem(REFRESH_TOKEN);
}
saveToken(token): void {
localStorage.setItem(ACCESS_TOKEN, token);
}
saveRefreshToken(refreshToken): void {
localStorage.setItem(REFRESH_TOKEN, refreshToken);
}
removeToken(): void {
localStorage.removeItem(ACCESS_TOKEN);
}
removeRefreshToken(): void {
localStorage.removeItem(REFRESH_TOKEN);
}
For accessing REST API, we will use the built-in Angular 10 feature HTTP Client module. Open and edit src/app/app.module.ts
then add this HTTP Client module to the @NgModule import array.
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule
],
Don’t forget to add imports to that module.
import { HttpClientModule } from '@angular/common/http';
Next, open and edit src/app/auth.service.ts
then declare the required OAuth client and secret and REST API URL as the constant variable.
const OAUTH_CLIENT = 'express-client';
const OAUTH_SECRET = 'express-secret';
const API_URL = 'http://localhost:3000/';
Also, add a constant variable that contains the headers for Form data and basic authorization that convert OAuth2 client and secret to basic auth token.
const HTTP_OPTIONS = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + btoa(OAUTH_CLIENT + ':' + OAUTH_SECRET)
})
};
Declare the required variable a the top of the class body.
redirectUrl = '';
Add the functions that handle error response and log message before the constructor.
private static handleError(error: HttpErrorResponse): any {
if (error.error instanceof ErrorEvent) {
console.error('An error occurred:', error.error.message);
} else {
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
return throwError(
'Something bad happened; please try again later.');
}
private static log(message: string): any {
console.log(message);
}
Inject the HttpClient and TokenService to the constructor.
constructor(private http: HttpClient, private tokenService: TokenService) {
}
Add the required API calls after the constructor.
login(loginData: any): Observable<any> {
this.tokenService.removeToken();
this.tokenService.removeRefreshToken();
const body = new HttpParams()
.set('username', loginData.username)
.set('password', loginData.password)
.set('grant_type', 'password');
return this.http.post<any>(API_URL + 'oauth/token', body, HTTP_OPTIONS)
.pipe(
tap(res => {
this.tokenService.saveToken(res.access_token);
this.tokenService.saveRefreshToken(res.refresh_token);
}),
catchError(AuthService.handleError)
);
}
refreshToken(refreshData: any): Observable<any> {
this.tokenService.removeToken();
this.tokenService.removeRefreshToken();
const body = new HttpParams()
.set('refresh_token', refreshData.refresh_token)
.set('grant_type', 'refresh_token');
return this.http.post<any>(API_URL + 'oauth/token', body, HTTP_OPTIONS)
.pipe(
tap(res => {
this.tokenService.saveToken(res.access_token);
this.tokenService.saveRefreshToken(res.refresh_token);
}),
catchError(AuthService.handleError)
);
}
logout(): void {
this.tokenService.removeToken();
this.tokenService.removeRefreshToken();
}
register(data: any): Observable<any> {
return this.http.post<any>(API_URL + 'oauth/signup', data)
.pipe(
tap(_ => AuthService.log('register')),
catchError(AuthService.handleError)
);
}
secured(): Observable<any> {
return this.http.get<any>(API_URL + 'secret')
.pipe(catchError(AuthService.handleError));
}
Now, we need to add an Angular HTTP Interceptor that intercepts the request and response from the REST API calls. Create a new file src/app/auth.interceptor.ts
then add these imports to that file.
import {Injectable} from '@angular/core';
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
import {Router} from '@angular/router';
import {throwError} from 'rxjs';
import {TokenService} from './token.service';
import {catchError, map} from 'rxjs/operators';
import {AuthService} from './auth.service';
Add the injectable class that implements the HttpInterceptor.
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
}
Add a constructor with Router, AuthService, and TokenService parameters.
constructor(
private router: Router,
private tokenService: TokenService,
private authService: AuthService) {
}
Add this intercept function after the constructor.
intercept(request: HttpRequest<any>, next: HttpHandler): any {
const token = this.tokenService.getToken();
const refreshToken = this.tokenService.getRefreshToken();
if (token) {
request = request.clone({
setHeaders: {
Authorization: 'Bearer ' + token
}
});
}
if (!request.headers.has('Content-Type')) {
request = request.clone({
setHeaders: {
'content-type': 'application/json'
}
});
}
request = request.clone({
headers: request.headers.set('Accept', 'application/json')
});
return next.handle(request).pipe(
map((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
console.log('event--->>>', event);
}
return event;
}),
catchError((error: HttpErrorResponse) => {
console.log(error.error.error);
if (error.status === 401) {
if (error.error.error === 'invalid_token') {
this.authService.refreshToken({refresh_token: refreshToken})
.subscribe(() => {
location.reload();
});
} else {
this.router.navigate(['login']).then(_ => console.log('redirect to login'));
}
}
return throwError(error);
}));
}
If there’s a token available, the request will intercept by headers that contain Authorization with Bearer token value and application/json content-type. Every successful response will be noted to the console log. Every error response will catch and specify if the response status is 401 (Unauthorized). Also, specified again the 401 to check the error message that contains “invalid_token”. If found, then execute the refresh token API calls otherwise, it will redirect to the login page.
Register that created AuthInterceptor class to the app.module.ts inside the provider’s array.
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
],
Don’t forget to add the imports.
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {AuthInterceptor} from './auth.interceptor';
#angular #security #javascript #programming #developer