ng5plus-auth

1.1.0 • Public • Published

ng5plus-auth

Angular 5+ authentication service. This service is used with Angular router.

Installation

$ npm install --save ng5plus-auth
 

Dependencies

Integration

1. app.module.ts

import { AppRoutesModule } from './app-routes.module';
import { CookiesService } from 'ng5plus-cookies';
import { AuthService } from 'ng5plus-auth';
 
const api_base_url = 'http://localhost:4444/api';
 
const auth_urls = {
  afterGoodLogin: '/{loggedUserRole}', // {loggedUserRole} -> 'admin' | 'customer'
  afterBadLogin: '/login',
  afterLogout: '/login'
};
 
const cookie_options = {
  // domain: 'localhost',
  path: '/',
  expires: 3, // expire in 3 days
  // expires: new Date('2018-10-31T03:24:00'),
  secure: false,
  httpOnly: false,
  sameSite: 'strict'
};
 
@NgModule({
  declarations: [],
  imports: [AppRoutesModule],
  providers: [
    CookiesService,
    AuthService,
    { provide: 'API_BASE_URL', useValue: api_base_url },
    { provide: 'AUTH_URLS', useValue:  auth_urls },
    { provide: 'COOKIE_OPTS', useValue: cookie_options },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
 

Notice: {loggedUserRole} is like variable which is replaced with user's role: admin, customer, ... User's role have to be returned by API.

2. app-routes.module.ts

// modules
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
 
// components
import { HomeComponent } from './pages/home/home.component';
import { LoginComponent } from './pages/login/login.component';
import { AdminComponent } from './pages/admin/admin.component';
import { CustomerComponent } from './pages/customer/customer.component';
import { NotfoundComponent } from './pages/notfound/notfound.component';
 
// services
import { IsLoggedService, HasRoleService, AutologinService } from 'ng5plus-auth';
 
// routes
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'login', component: LoginComponent, canActivate: [AutologinService] },
  { path: 'admin', component: AdminComponent, canActivate: [IsLoggedService, HasRoleService] },
  { path: 'customer', component: CustomerComponent, canActivate: [IsLoggedService, HasRoleService] },
  { path: '404', component: NotfoundComponent },
  { path: '**', redirectTo: '/404' }
];
 
 
@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  providers: [IsLoggedService, HasRoleService, AutologinService],
  exports: [ RouterModule ],
  declarations: [
    HomeComponent,
    AdminComponent,
    CustomerComponent,
    NotfoundComponent
  ]
})
 
export class AppRoutesModule {}

Methods

login(creds: Credentials): Observable

Login with const creds = {username: 'xxxx', password: 'yyyy'} and redirect to URL defined by auth_urls.afterGoodLogin.

Required 'creds' properties are:

interface Credentials {
  username: string;
  password: string;
}

logout(): void

Logout and re.

getLoggedUserInfo(): LoggedUser

Get logged user info. This object is returned by API and stored into cookie auth_loggedUser.

Required user object properties are:

interface LoggedUser {
  first_name: string;
  last_name: string;
  address?: string;
  city?: string;
  country?: string;
 
  phone?: string;
  email: string;
  website?: string;
 
  misc?: any;
 
  username: string;
  password?: string;
 
  role: string;
  is_active?: boolean;
  jwt_token?: string;
}

so API response should return those fields.

getJWTtoken(): string

Take JWT token from 'auth_jwtToken' cookie. This token is fetched from API and stored in 'auth_jwtToken' cookie.

API response

In order to work properly this service requires following JSON response or simmilar:

{
    "success": true,
    "message": "Login was successful. JWT is generated and you can use it in API request header. Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVhZjdmYTZmMjlmOWJlMTg1MmRkNjMyZSIsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE1Mjc5MzYxNjR9.QI78esEzZkxpkuMhWGqPASGvRvrti1GNWM7ozxnvyfU",
    "jwtToken": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVhZjdmYTZmMjlmOWJlMTg1MmRkNjMyZSIsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE1Mjc5MzYxNjR9.QI78esEzZkxpkuMhWGqPASGvRvrti1GNWM7ozxnvyfU",
    "loggedUser": {
        "role": "admin",
        "is_active": true,
        "_id": "5af7fa6f29f9be1852dd632e",
        "first_name": "Marko",
        "last_name": "Adminić",
        "address": "Radića 23",
        "city": "Osijek",
        "country": "Croatia",
        "phone": "+385-93-2111-222",
        "email": "test@uniapi.com",
        "website": "www.uniapi.org",
        "username": "admin",
        "password": "--removed--",
        "created_at": "2018-05-13T08:42:23.363Z",
        "updated_at": "2018-06-02T10:42:44.175Z",
        "__v": 0,
        "jwt_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVhZjdmYTZmMjlmOWJlMTg1MmRkNjMyZSIsInVzZXJuYW1lIjoiYWRtaW4iLCJpYXQiOjE1Mjc5MzYxNjR9.QI78esEzZkxpkuMhWGqPASGvRvrti1GNWM7ozxnvyfU"
    }
}

Method Implementation

login({username: , password: })

/pages/login/login.html

<form id="login-form" action="#" method="POST" novalidate="" [formGroup]="loginFG">
    <div class="form-group">
        <label for="username">Username</label>
        <input type="text" class="form-control underlined" placeholder="Your username" required formControlName="username"> </div>
    <div class="form-group">
        <label for="password">Password</label>
        <input type="password" class="form-control underlined" placeholder="Your password" required formControlName="password"> </div>
    <div class="form-group">
        <button type="button" class="btn btn-block btn-primary" (click)="login()">Login</button>
    </div>
    <div>
        <p class="alert alert-warning" *ngIf="!!err && !!err.message">{{err.message}}</p>
    </div>
</form>

/pages/login/login.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
import { AuthService } from 'ng5plus-auth';
 
 
@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
 
  loginFG: FormGroup;
  err: Error;
 
  constructor(private fb: FormBuilder, private authService: AuthService) { }
 
  ngOnInit() {
    this.loginFG = this.fb.group({
      username: '',
      password: ''
    });
  }
 
 
  login() {
    const creds = this.loginFG.value; // {username: , password: }
 
    this.authService.login(creds)
      .subscribe((loggedUser: any) => {
        const jwtToken = this.authService.getJWTtoken();
        console.info('LOGGED USER:: ', loggedUser, ' jwtToken=', jwtToken);
      }, (err) => {
        this.err = err.error;
        setTimeout(() => {
          this.err = null;
        }, 2100);
        console.error('ERROR: ', err);
      });
  }
 
}
 

logout()

/pages/admin/admin.html

<a class="dropdown-item" [routerLink]="" (click)="logout()"><i class="fa fa-power-off icon"></i> Logout </a>

/pages/admin/admin.ts

import { Component, OnInit } from '@angular/core';
import { AuthService } from 'ng5plus-auth';
 
@Component({
  selector: 'app-admin',
  templateUrl: './admin.component.html',
  styleUrls: ['./admin.component.css']
})
export class AdminComponent implements OnInit {
 
  loggedUser: any;
 
  constructor(private authService: AuthService) {
    this.loggedUser = authService.getLoggedUserInfo();
  }
 
  ngOnInit() {
  }
 
  logout() {
    console.log('LOGOUT:: ');
    this.authService.logout();
  }
 
}

Protections

Route Gurads

To protect angular routes from unauthorized access this service uses canActivate route guards:

  • IsLoggedService check if user is logged with valid username:password.
  • HasRoleService check if user has appropriate role, e.g. role must me contained in URL string, for example: /admin/dashboard is valid for 'admin' role but /customer/dashboard is not valid for 'admin' role. This guard must be applied after IsLoggedService guard.
  • AutologinService check if user is already logged. If yes then automatically redirect to URL defined by auth_urls.afterGoodLogin. This guard should be applied only on login page e.g. /login angular route.

API HTTP Request protections

To protect each API request use JWT Token from 'auth_jwtToken' cookie and use it in HTTP header 'Authorization: JWT ' as HTTP interceptor for each API request.

Example

This npm package is integrated in ng5plus-startup so it is full integration example and shows how to use it regularly.

Licence

Created by Saša Mikodanić under MIT licence.

Package Sidebar

Install

npm i ng5plus-auth

Weekly Downloads

0

Version

1.1.0

License

MIT

Unpacked Size

18.5 kB

Total Files

10

Last publish

Collaborators

  • smikodanic