@elderbyte/ngx-auth

[![CI Status](https://travis-ci.org/ElderByte-/ngx-auth.svg?branch=master)](https://travis-ci.org/ElderByte-/ngx-auth) [![npm version](https://badge.fury.io/js/%40elderbyte%2Fngx-auth.svg)](https://badge.fury.io/js/%40elderbyte%2Fngx-auth)

Usage no npm install needed!

<script type="module">
  import elderbyteNgxAuth from 'https://cdn.skypack.dev/@elderbyte/ngx-auth';
</script>

README

CI Status npm version

ngx-auth

Angular authentication library based on the OpenId Connect standard (OAuth & Signed JWTs).

The major version designates the supported Angular version. For example, 6.x.x means it supports Angular 6.

Scope

Current

  • OIDC compatible
  • Suppurts OIDC discovery and auto configuration
  • Local login via backend service
  • Remote login supporting OAuth2 login
  • Saving JWT in local storage
  • In-memory Principal created by JWT parsing
  • Parsing user tokens from URL like /?access_token=myNiceJwt.token
  • Mock support for easy development handling
  • Supports HttpClient request interceptors for automated JWT bearer token injection
  • Refreshtoken support
  • Silent login refresh support (iframe)
  • Supports Angular 7

Future

Usage

Install via npm

npm i @elderbyte/ngx-auth --save

Import via JS/TS import statement

import {} from "@elderbyte/ngx-auth";

Configuration of AuthService (simplified)

Register module simply in your AppModule like this:

@NgModule({
  ...
  imports: [
    ...
    SimpleWebStorageModule.forRoot(),
    ElderAuthModule.forRoot({
      localLoginRoute: '/login',
      accessDeniedRoute: '/access_denied',
      obtainTokenUrl: environment.apiUrl + '/oauth/token',
    }),
    ...
  ],
  ...
])
export class AppModule { }

Beware, this module depents on these two libraries:

Display DOM elements (by Role)

In case you need to hide certain elements when users do not belong to a specific role, use hasRole:

<div *hasRole="['ADMIN']">
  <!-- my incredible content -->
</div>

Guarding URLs (simplified)

Besides obtaining a token from the server, of course you may want to protect routes by restricting access to authenticated users only. This is pretty straight forward:

const appRoutes: Routes = [
  {
    path: 'login',   // Unprotected url
    component: LoginComponent,
  },
  {
    path: 'orders',
    canActivate: [SimpleAuthGuard],   // Only accessible for logged in users
    component: OrderBrowserComponent,
  }
];
  • For more fine gradient role based rules, see belows HasRoleGuard.

Guarding URLs HasRoleGuard

To prevent users from accessing parts of your application if they lack certain roles, you can use a role based router guard:

Assume you have the following route definition:

  {
    path: 'app/customers',
    component: CustomerBrowserComponent,
  }

Just add the HasRoleGuard in canActivate and specify the allowed roles under the data.roles key:

  {
    path: 'app/customers',
    component: CustomerBrowserComponent,
    canActivate: [HasRoleGuard],
    data: {
      roles: ['USER']
    }
  }

Configuration of AuthService (customized)

export class JwtAuthConfig {

    /**
     * Specify the OAuth client id. (Required for OAuth login)
     */
    clientId?: string;

    /**
     * OAuth Login Page.
     * If defined, the user is redirected to the OAuth login page.
     */
    oAuthLoginUrl?: string;


    /**
     * An Angular route which is invoked on access denied.
     * Defaults to '/accessdenied'
     */
    accessDeniedRoute?: string;


    /**
     * An Angular route which is invoked on login requested
     * (Used if no OAuth API is defined)
     */
    localLoginRoute?: string;

    /**
     * Provides URL for obtaining JWT for a given grant.
     *
     * grant_type: password -> Get a JWT for your credentials (user & pass)
     * grant_type: refresh_token    -> Get a JWT for the refresh token
     *
     * Should be an URL like '/oauth/token'
     */
    obtainTokenUrl?: string;


    /**
     * Provides the ability to automatically refresh JWTs
     * using refresh tokens.
     *
     * (Requires a valid obtainTokenUrl)
     */
    refresh?: {

        /**
         * Set the refresh strategy. (refresh disabled by default)
         */
        strategy?: RefreshStrategy;

        /**
         * By default, the obtain-token url is used to get a refreshed JWT.
         * Setting this url overwrite this default.
         */
        refreshTokenUrl?: string;

        /**
         * The interval for the periodic strategy in milli-seconds
         */
        interval?: number;

        /**
         * The minimal TTL before the token is refreshed in milli-seconds
         */
        minTtl?: number;
    };


    /**
     * Configure the token storage
     */
    tokenStorage?: {

        /**
         * The storage type where to store the secrets.
         */
        type?: StorageType;

        /**
         * The storage key name for the access_token
         */
        accessTokenKeyName?: string;

        /**
         * The storage key name for the refresh_token
         */
        refreshTokenKeyName?: string;
    };

    /**
     * JWT Field names in accessToken used by the backend.
     */
    jwt?: {
        usernameField?: string;
        subjectField?: string;
        rolesField?: string;
        issuerField?: string;
        audienceField?: string;
        realmField?: string;
    };


    /**
     * JwtResponse Field names in obtaining token by the backend.
     */
    jwtResponse?: {
        accessTokenField?: string;
        refreshTokenField?: string;
    };

    /**
     * Mock configuration to simulate a logged in user.
     */
    mock?: {
        enable?: boolean;
        user?: {
            subject?: string;
            name?: string;
            roles?: string[];
            issuer?: string;
            audience?: string[];
            realm?: string;
        };
        token?: {
            accessToken?: string;
            refreshToken?: string;
        };
    };
}

OAuth Sample Configuration

@NgModule({
  declarations: [
  imports: [
    HttpClientModule,
    ElderAuthModule.forRoot(
      {
        clientId: 'myapp',
        oAuthLoginUrl: 'https://my.oauth.server/myrealm/login'
        accessDeniedRoute: 'app/accessdenied',
        mock: {enable: false } 
      }
    )
  ]
})
export class AppModule {
}

OIDC Sample Configuration

@NgModule({
  declarations: [
  imports: [
    HttpClientModule,
    ElderAuthModule.forRoot(
        {
            issuerUrl: 'http://localhost:8099/auth/realms/demo',
            clientId: 'demo-client',
            scope: 'openid profile email',
            accessDeniedRoute: 'app/accessdenied'
       }
    )
  ]
})
export class AppModule {
}

Local Login Sample Configuration

@NgModule({
  declarations: [
  imports: [
    HttpClientModule,
    ElderAuthModule.forRoot(
      {
        localLoginRoute: 'app/login',
        accessDeniedRoute: 'app/accessdenied',
        obtainTokenUrl: '/api/oauth/token',
      }
    )
  ]
})
export class AppModule {
}

Local Login Component

The LoginComponent is not included in this repository, but implementing it is easy as a pie:

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
})
export class LoginComponent {
  username: string;
  password: string;

  constructor(private authService: AuthService) { }

  login() {
    this.authService
      .loginWithCredentials(this.username, this.password)
      .subscribe(principal => {
          console.log(principal);
      }, error => {
          console.log(error);
      });
  }
}

The template may look something like this:

<div class="form-group">
  <label>Username</label>
  <input [(ngModel)]="username">
</div>
<div class="form-group">
  <label>Password</label>
  <input type="password" [(ngModel)]="password">
</div>

<button (click)="login()">Login</button>

Development

After you have added and tested a new feature, publish a new version on npm:

  1. Increment the version in package.json and dist/package.json - they must have the same version
  2. Build the library into the /dist directory by running npm run build from the project root directory.
  3. Commit the compiled /dist changes
  4. Run npm publish dist --access=public from the project root directory or npm publish --access=public from within the dist directory.

Note --access=public is only neccessary if you publish the package the first time. If you omit this parameter the first time, you'll get a error message that you need a paid account.

License

MIT © ElderByte AG