npm package: @sigao/ng-auth0
Description: This library is an Angular wrapper for Auth0's auth0-spa-js library (currently 1.8.1).  It provides much of the functionality covered in Auth0's Angular tutorials in one importable module that can be configured for common use cases.
Latest version: 2.0.1

# NgAuth0

This library is an Angular wrapper for Auth0's auth0-spa-js library (currently 1.8.1).  It provides much of the functionality covered in Auth0's Angular tutorials in one importable module that can be configured for common use cases.

## Setup

### 1. Install the package

Run `npm install @sigao/ng-auth0`

### 2. Create configuration object

The configuration object extends Auth0's configuration object with custom properties that tell the library how to handle the authentication cycle.  These properties can be found [here](https://auth0.github.io/auth0-spa-js/interfaces/auth0clientoptions.html).  For the sake of this we're going to only focus on NgAuth0 configurations.

**NOTE: Fields marked [Requires Configuration] MUST be configured in your Auth0 tenant.  A path to the associated setting is provided.**

```
const ngAuth0Config = {

  //Auth0 SPA configuration properties here
  // ...
  // ...

  // Defines the logout redirect url.  Because this is an external redirect the url must be a full url.
  // [Requires Configuration]: Can be found in Applications > Your SPA App > Settings > Allowed Logout URLs
  logoutRedirectPath: string;

  // The final route Angular will use after authentication.  This is an internal route using angular's router 
  // [Default Value]: { commands: ['/'] }
  loginReroute?: Auth0Navigate;

  // Specifies where the logged in guard will reroute the user in the event of an unauthorized navigation
  // [Default Value]: { commands: ['/login'] }
  loggedInGuardReroute?: Auth0Navigate;

  // Activates an HTTP interceptor to append the Bearer Token to all outbound requests.  If your application requires
  // more control of the Bearer Tokens, leave this blank
  // [Default Value]: false
  intercept_http?: boolean;
}

```

The Auth0Navigate object simply defines the properties expected by Angular's router as follows
```
{
  commands: any[],
  navigationExtras: NavigationExtras // Angular router navigation extras
}
```


### 3(a) Configure Auth0 via module definitions (optional)

Once your configuration object is created, the last step is to provide it to the module.


**app.module.ts**
```
@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule, // This provides RouterModule which is required for this library
    NgAuth0Module.forRoot(ngAuth0Config) // Define the config object above, or import from another file
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

### 3(b) Configure Auth0 via the service (optional)

If your application requires the configuration values to be provided after app initialization (such as from some sort of CI/CD process), it may be necessary to configure auth0 via the service.  

**app.module.ts**

Do not provide config object
```
@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule, // This provides RouterModule which is required for this library
    NgAuth0Module // forRoot is not needed
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```



**app.component.ts**

Instead, provide config object to the service
```
@Component({ ... })
export class AppComponent {
  constructor(private auth0Service: Auth0Service) {

    // this will setup NgAuth0.  Must run before any other NgAuth0 functionality
    this.auth0Service.init(ngAuth0Config);
  }
}
```

### 4. Create the callback route

One of the key aspects of Auth0 is the redirect_uri property.  This is the url that Auth0 will navigate to after login.  For this to work properly, you will need to specify the redirect_uri string and create a route in your angular component

**NOTE: this must be configured in your Auth0 tenant as well: Applications > Your SPA App > Settings > Allowed Callback URLs**

It is recommended to use this as your redirect_uri so that no matter what environemnt your app is deployed to the url will be correct. 
```
redirect_uri: `${location.protocol}//${window.location.host}/callback`
```

Now that Auth0 knows where to redirect, the next step is to create a page somewhere in the application with the path "callback", otherwise the router will not recognize the path being navigated to.  Below is an exmaple of what this would look like, however you are free to define the callback however you'd like, provided you also include the url in your Auth0 tenant **Allowed Callback URLs**. 

**callback.component.ts**

```
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CallbackComponent } from './pages/callback/callback.component';

@NgModule({
    imports: [
        RouterModule.forRoot([
            {
                path: 'callback',
                component: CallbackComponent
            }
        ])
    ],
    exports: [RouterModule]
})
export class AppRoutingModule { }
```

### 5. Add app entry functions

The last step is to call the functions that will analyze the callback url and authenticate the user.

In your **app.component.ts** import Auth0Service and add this call `this.auth0Service.localAuthSetup();` to constructor.  If you're using the method defined in 3(b) make sure this call is after `this.auth0Service.init(...)`

In your **callback.component.ts** import Auth0Service and add this call to the constructor `this.auth0Service.handleAuthCallback();`.  This will handle creating the session and any post-login redirecting specified.

## Usage

### Auth0Service

The NgAuth0 service provides a number of functions to be used throughout the application.  Functions that return an observable have an accompanying property that gets updated as the observable completes. **NOTE: appState can not be refreshed via resubscription, it is only available once after login.**  

| Name                                          | Return type         | Description                                                                |
| :-------------------------------------------- |:------------------- | :------------------------------------------------------------------------- |
| init(config: Auth0Config)                     | `void`              | Initializes Auth0 and provides configuration to other services             |
| localAuthSetup()                              | `void`              | Handles authentication on app entry.  Place in app.component.ts            |
| handleAuthCallback()                          | `void`              | Interprets route parameters in the callback url, authenticates, and refreshes all observables for auth values  |
| login(loginRerouteOverride?: Auth0Navigate) | `void`              | Triggers the Auth0 login sequence.  If `loginRerouteOverride` is provided the service will reroute to this location rather than what is defined in the `Auth0Config` object |
| loginWithState(state: { [key:string]:any }, loginRerouteOverride?: Auth0Navigate) | `void`              | Similar to standard `login` function.  The provided state object will be available after redirect in the service's `appState` property |
| logout()                                      | `void`              | Triggers Auth0 logout sequence.  Will redirect to the url provided in the `Auth0Config` object |
| getIsAuthenticated()       | `Observable<boolean>`| Returns observable with fresh authentication value and updates the `isAuthenticated` property on the service | 
| getUserProfile (options?: GetUserOptions)  | `Observable<any>`| Returns observable with fresh user profile value and updates the `userProfile` property on the service  |
| getIdTokenClaims(options: GetIdTokenClaimsOptions) | `Observable<any>`| Returns observable with fresh ID Token Claims value and updates the `idTokenClaims` property on the service |
| getTokenSilently(options?: GetTokenSilentlyOptions)| `Observable<string>` | Returns observable with fresh token value and updates the `token` property on the service |
| getAppState                                        | `Observable<any>`    | Returns observable with app state after login.  Only updates once on login  |


The following are properties are also provided by the service:

| Name              | Type                    | Description                                              |
| :-----------------|:----------------------- | :------------------------------------------------------- |
| config            | `Auth0Config`           | Copy of config object provided to the service            |
| isAuthenticated   | `boolean`               | Authentication status. Updated via "getIsAuthenticated"  |
| userProfile       | `any`                   | User profile object.  Updated via "getUserProfile"       |
| idTokenClaims     | `any`                   | Id Token Claims object.  Updated via "getIdTokenClaims"  |
| token             | `string`                | Authentication token.  Updated via "getTokenSilently"    |
| appState          | `{ [key: string]: any }`| App State object.  Updated on initial login.             |


#### Example: Using observables

Each "get" method on the service returns an observable that, when subscribed to, will return the value described and also update the associate property on the service.  This can be used like so:

In your component
```
constructor(private auth0Service: Auth0Service){
  this.isAuthenticated = this.auth0Service.getIsAuthenticated();
}
```

then in your html
```
<div *ngIf="isAuthenticated | async">This will show once authenticated!</div>
```
#### Example: using properties

If you would rather only us the variables on the service, you can do something like this in your html:
```
<div *ngIf="auth0Service.isAuthenticated">This will show once authenticated!</div>
```
However, keep in mind that in this case isAuthenticated will only be updated once on startup.  If you want to get a fresh value, you'll have to use `auth0service.getIsAuthenticated().subscribe()` which will update the `isAuthenticated` property once completed.



### Auth0LoggedInGuard

The Auth0LoggedInGuard is used to prevent unauthorized access to routes within the system.  It can be included in all 3 access prevention arrays (canActivate, canActivateChild and canLoad).

**NOTE: Using this guard in both canActive and canActivateChild will use the redirect strategy for unauthorized access attempts, however canLoad will simply block navigation.  This is a byproduct of Angular's API.**

Below is an example of usage:

```
@NgModule({
    imports: [
        RouterModule.forRoot([
            {
                path: 'authorized-path',
                component: TestPathComponent,
                canActivate: [Auth0LoggedInGuard], 
            }
        ])
    ],
    exports: [RouterModule]
})
export class AppRoutingModule { }
```


#### Guard redirect strategy

##### Default (No provided configuration)

By default, the guard will reroute unauthorized users to `/login`.


##### Configured Default (Provided in NgAuth0 configuration)

If the user has chosen to provide a Auth0Navigate object via the `loggedInGuardReroute` property, the system will default all unauthorized access attempts to the location provided.

##### Individual Override 

If needed, rerouting can be configured for an individual route.  This is accomplished by creating a `loggedInGuardReroute` property inside the route's `data` property like so:

```
 {
    path: 'authorized-path',
    component: TestPathComponent,
    canActivate: [Auth0LoggedInGuard], 
    data:{
        loggedInGuardReroute: {
            commands: ['/new-override-path']
            // 'navigationExtras' can also be added here!
        }
    }        
}
```



## Testing 

When unit testing classes that rely on the Auth0Service, import the NgAuth0TestingModule into your test bed like so:

```
import { NgAuth0TestingModule } from 'ng-auth0';

 beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports:[
        NgAuth0TestingModule
      ]
    }).compileComponents();
  }));
```

The testing module provides mock versions of the Auth0Service and Auth0LoggedInGuard services. For most use cases, no further action is needed, however if you need to return specific values the provided services can be spyed on like so:

```
  beforeEach(() => {
    service = TestBed.get(Auth0Service);
    const getUserSpy = spyOn(service, 'getUser$').and.returnValue(of(/* Your custom value*/));
  });
```

## Change Log

### 2.0.1

* Fixed intercept_http defaulting to `true` instead of `false` on missing configuration

### 2.0.0 New hotness

* Rewrote observable strategy to favor factory functions rather than properties
  * Note that subscribing to these observables automatically gets fresh data, and also updates the associated property on the service  
* Fixed bug that prevented fresh values getting sent to the "isAuthenticated" streams on login
* normalized access to all variables by providing an observable and traditional property for all values
* Included Auth0 1.8.1 in dependencies to prevent users from having to install the correct version themselves

### 1.2.0

* Added NgAuth0TestingModule to provide mock services for unit testing
* Added `loginWithState` function and accompanying `appState` property for passing state to the authorization callback

### 1.1.0

* Updated peer dependencies
* Increased code coverage significantly
* Added idTokenClaims$ observable to view raw claims data from the authenticated user

### 1.0.1 - 1.0.2 New Org!

* Migrated package to our company org

### 1.0.0 
Published!

## Powered By [Sigao](http://www.sigao.io/)

This package is designed to be used within our tech stack to support our development efforts.  While significant effort has been made to ensure that these packages are tested and maintained, mistakes happen, and there is a good possibility the bug won't be addressed unless it directly impacts our specific use case.   

If you encounter a bug that you'd like addressed, feel free to reach out to us and we'll see what we can do!