100 Security: Authentication. Client Side. - chempkovsky/CS82ANGULAR GitHub Wiki

Two aspects

Security has two aspects: Authentication and Authorization

Authentication at the client side

Authentication implemented using OAuth 2.0 Bearer Token Usage. Executing 00200-app-glbl-auth.json generates a basic ready to use implementation of such authentication.

  • Authentication consists of:
    • src\app\shared\services\app-glbl-login.service.ts to call backend Web Api services
    • src\app\shared\services\app-glbl-settings.service.ts to hold Bearer Token which is returned after login. (It's AuthInfo-property and UserName-property)
    • src\app\shared\services\app-glbl-settings.service.ts to define http headers for other requests to backend services. (It's getAuthInfoHeader()-method)
    • src\app\shared\components\app-glbl-chngpswd-page to change password
    • src\app\shared\components\app-glbl-login-page to login
    • src\app\shared\components\app-glbl-logout-page to logout
    • src\app\shared\components\app-glbl-register-page to register
    • `src\app\app.component.ts' application component which has global menu item
Click to show the picture

project structure

Important persistence note

As it was mentioned above src\app\shared\services\app-glbl-settings.service.ts-service holds Bearer Token in a local variable which is not persistent!!!

    protected authInto: any = null;
    public getAuthInto(): any {
        return this.authInto;
    }
    public setAuthInto(info: any): any {
        if(typeof info === 'undefined') {
            this.authInto = null;
        } else {
            this.authInto = info;
        }
    }
    ...
    public userName: string|null=null;

So after reloading any page using the browser the Bearer Token will be lost.

picture

There is no secure standard of how to persist important information on the client side (we mean session, local storage and so on). The developer should decide how to implement such a persistence.

Bearer Token usage note

src\app\shared\services\app-glbl-settings.service.ts-service is available to any generated component. For instance:

export class LitGenreViewEformComponent implements OnInit, IEventEmitterPub {
    ...
    constructor(private frmSrvLitGenreView: LitGenreViewService, public dialog: MatDialog, protected appGlblSettings: AppGlblSettingsService ) {
    ...

On the other hand, every method that makes a Web Api call refers to src\app\shared\services\app-glbl-settings.service.ts-service as well. For Instance:

  @Injectable({
    providedIn: 'root'
  })
  export class LitGenreViewService {
    ...
    serviceUrl: string;  
    constructor(private http: HttpClient, protected appGlblSettings: AppGlblSettingsService) {
       this.serviceUrl = this.appGlblSettings.getWebApiPrefix('LitGenreView') + 'litgenreviewwebapi';  
    }
    ...

src\app\shared\services\app-glbl-settings.service.ts service is used to get URL and to prepare http headers. If Bearer Token is available it will be added to the header with src\app\interceptors\app-glbl.interceptor.ts. For instance:

import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
import { AppGlblSettingsService } from './../services/app-glbl-settings.service';

@Injectable()
export class AppGlblInterceptor implements HttpInterceptor {
  constructor(private appGlblSettings: AppGlblSettingsService) {}
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    let authInfo = this.appGlblSettings.getAuthInto();
    if(typeof authInfo === 'undefined') {
        return next.handle(req);
    }
    if(authInfo === null) {
        return next.handle(req);
    }
    if ((typeof authInfo.access_token === 'undefined') || (typeof authInfo.token_type === 'undefined')) {
        return next.handle(req);
    }
    if ((authInfo.access_token === null) || (authInfo.token_type === null)) {
        return next.handle(req);
    }
    const authReq = req.clone({ setHeaders: { Authorization: authInfo.token_type + ' ' +  authInfo.access_token} });
    return next.handle(authReq);
  }
}

Obtaining Bearer Token

  • open doSubmit()-method of the shared/components/app-glbl-login/app-glbl-login.component.ts-file
    doSubmit() {
        if(typeof this.loginFormGroup === 'undefined') return;
        if(this.loginFormGroup === null) return;
        if (this.loginFormGroup.invalid) {
            this.loginFormGroup.markAllAsTouched();
            return;
        }
        this.scrtSvr.login(this.loginFormGroup.controls['username'].value, this.loginFormGroup.controls['password'].value)
        .subscribe({
            next: (respdata: any ) => { // success path
                this.appGlblSettings.setAuthInto(respdata);
                this.appGlblSettings.userName = this.loginFormGroup.controls['username'].value;
                this.router.navigate(['/']);

                this.scrtSvr.getPermissions().subscribe({
                    next: (rspdt: {modelName: string, userPerms: number}[]) => {
                        this.appGlblSettings.vwModels = {};
                        rspdt.forEach((r: {modelName: string, userPerms: number}) => {
                            this.appGlblSettings.vwModels[r.modelName] = r.userPerms;
                        });
                    },
                    error: (error) => { 
                        this.appGlblSettings.showError('http', error)
                    }
                });
            },
            error: (error) => { // error path
                this.appGlblSettings.showError('http', error)
            }
        });
    }

Cleaning up Bearer Token

  • open doSubmit()-method of the shared/components/app-glbl-login/app-glbl-logout.component.ts-file
    doSubmit() {
        this.appGlblSettings.perms = [0,0,0,0,  0,0,0,0,  0,0,0,0,  0,0,  1,0,0];
        this.appGlblSettings.setAuthInto(null);
        this.appGlblSettings.userName = null;
        this.appGlblSettings.vwModels = {};
        this.router.navigate(['/']);
    }
⚠️ **GitHub.com Fallback** ⚠️