An Angular library for the Open Id Connect Protocol implementing:
- Discovery Document
- Code Flow
- Refresh Tokens
- Automatic Token Refresh before token expiring
- Session Checks
- Code Flow in popup
For Angular 8, use the v8.x
latest release.
For Angular 9, use the v9.x
latest release.
Using master
branch is not recommended.
I am not a security expert. I've followed my best the RFCs, please I encourage you to review my work and report issues if you suspect something may be wrong.
Use at your own risk.
Why another OIDC library? I needed a library that is:
- Compilant with the OIDC protocol.
- Returns descriptive error messages.
- Works well with observables.
- Integrates seamlesly with NGRX.
- PRs are merged with caution.
- OIDC protocol practically does not change, so I expect only bugfixes to be merged once it's stable.
- Respects Semantic Versioning.
- Mayor versions to be released together with Angular's.
- Basic features. More features only if I need them (refresh tokens, session checks) through other modules/packages.
Install from NPM
yarn add angular-simple-oidc
Authorization Code Flow with PKCE will be used, so your Identity Provider (idp) must support this. Your idp may require a client secret as well.
The redirect URI, by default will be http://yourapp.example.com/oidc-token-callback
.
You can change the path by configuring the tokenCallbackRoute
.
You will then need to supply the configuration for OIDC.
Configuration can be provided statically for in the app.module
file.
You could use environment.ts
config replacement for different prod/staging/development settings.
i.e:
WARNING: The clientSecret should not be treated as a secret. Some IdentityProviders require a secret for Code Flow, some don't.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import {
AngularSimpleOidcModule,
AutomaticRefreshModule,
SessionManagementModule,
PopupAuthorizationModule
} from 'angular-simple-oidc';
@NgModule({
imports: [
BrowserModule,
AngularSimpleOidcModule.forRoot({
clientId: 'example.client',
// clientSecret: 'myDummySecret',
openIDProviderUrl: 'http://my.oidc.provider.com',
scope: 'openid profile offline_access',
// Other relevant configs.
// tokenCallbackRoute: 'oidc-token-callback'
}),
// For automatic token refresh.
// AutomaticRefreshModule,
// For Session Management (read below)
// SessionManagementModule.forRoot({
// iframePath: "assets/oidc-iframe.html"
// })
// For popup code flow (read below)
// PopupAuthorizationModule.forRoot({
// childWindowPath: 'assets/oidc-iframe.html'
// }),
],
declarations: [
AppComponent,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
The downside of using static configuration is that your config ends up hard-coded in your app bundle. You can customize the configuration provision by using a service and overriding Angular Simple OIDC configuration providers.
Check the custom-config
for a complete example
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Provider } from '@angular/core';
import { AppComponent } from './app.component';
import { AngularSimpleOidcModule, AUTH_CONFIG, SessionManagementModule, SESSION_MANAGEMENT_CONFIG } from 'angular-simple-oidc';
import { ExternalConfigService } from './external-config.service';
import { HttpClientModule } from '@angular/common/http';
import { map } from 'rxjs/operators';
// needs to be exporter for AOT
export function getExternalAuthConfigFactory(externalConfig: ExternalConfigService) {
return externalConfig.config$
.pipe(map(c => c.auth));
}
export const EXTERNAL_AUTH_CONFIG_PROVIDER: Provider = {
provide: AUTH_CONFIG,
useFactory: getExternalAuthConfigFactory,
deps: [ExternalConfigService],
};
export function getExternalSessionConfigFactory(externalConfig: ExternalConfigService) {
return externalConfig.config$
.pipe(map(c => c.session));
}
export const EXTERNAL_SESSION_CONFIG_PROVIDER: Provider = {
provide: SESSION_MANAGEMENT_CONFIG,
useFactory: getExternalSessionConfigFactory,
deps: [ExternalConfigService],
};
@NgModule({
imports: [
BrowserModule,
HttpClientModule,
// Do not provide a static config here.
AngularSimpleOidcModule.forRoot(),
SessionManagementModule.forRoot()
],
providers: [
EXTERNAL_AUTH_CONFIG_PROVIDER,
EXTERNAL_SESSION_CONFIG_PROVIDER,
],
declarations: [
AppComponent,
],
bootstrap: [AppComponent],
})
export class AppModule { }
Currently you can only provide scopes in the configuration. WIP #11
When using the router, you can use the AuthGuard to prevent a route from loading without a valid Access Token. If the token is not valid or expired, Code Flow will be started.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard } from 'angular-simple-oidc';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
{
path: '',
canActivate: [AuthGuard],
component: HomeComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
You can manually start the Code Flow injecting the AuthService
and calling startCodeFlow()
.
This will trigger a redirect to the IDP authorize
(aka login) page.
You normally want to provide a never-stopping loading on subscription, since the window will be redirected. i.e:
export class MyComponent {
public loadingForEver = false;
constructor(private readonly auth: AuthService) { }
public login() {
this.auth.startCodeFlow()
.subscribe(()=>{
this.loadingForEver = true;
});
}
}
When the IDP redirects back to your app, angular-simple-oidc
will take care of parsing the callback URL, validating and storing tokens and redirecting back to the state where you were when you started the Code Flow.
You can obtain the tokens by using the AuthService
observables. i.e:
export class MyComponent {
constructor(private readonly auth: AuthService) {
auth.accessToken$.subscribe(token =>{
console.info(`Current Access token: ${token}`);
// This will return the current token (it's a BehaviorSubject)
// and will emit when a new token is obtained.
// Note that a token will be returned even if it's expired
});
// Remember to takeUntil(this.destroyed$) or use the async pipe to prevent memory leaks.
}
}
Note that a token will be returned even if it's expired;.
To check if the user "is logged in", meaning that has an Access Token and the token is not yet expired you must use the isLoggedIn$
observable instead.
To log out the user, use the AuthService.endSession()
. Will redirect to the log out page of the IdentityProvider.
The AutomaticRefreshModule
will attempt to refresh the token using Refresh Tokens just before the tokens expire.
The SessionManagementModule
will use an iframe to an endpoint in the Identity Provider to check if the session is still active.
If the session has finished, SessionTerminatedEvent
will be fired.
You'll need to provide an HTML file in your /assets
directory with the below contents:
<html>
<!-- This file is required for angular-simple-oidc Session Management -->
<body>
<script>
(window.opener || window.parent).postMessage(location.href, location.origin);
</script>
</body>
</html>
Code flow can be executed in a poup using the PopupAuthorizationModule
. This is most useful when the SessionTerminatedEvent
has been fired, and the user may log-in using the same credentials without loosing the current app state, since there's no redirect. Be aware that the user may log-in using different credentials, to which you'll have to react accordingly.
To configure the popup, firstinclude the module on your app.module
and provide a childWindowPath
which can be the same file as the iframeUrl
of the SessionManagementModule
You'd then use the AuthorizeEndpointPopupClientService
like so:
class MyComponent {
constructor(
private readonly authorizePopupClient: AuthorizeEndpointPopupClientService,
) { }
public popup() {
this.authorizePopupClient.startCodeFlowInPopup()
.subscribe();
}
}
Check the home.component.ts
for a full example