We’ll be building a basic Angular application with the help of the ng-oidc-client library and an IdentityProvider of your choice to enable a user to authenticate either through a popup or redirect.
At the end, through a series of posts we will be looking into how to protect our routes and use a bearer token to access API endpoints. We’ll explore several new ways to take advantage of having authentication part of your application state using NgRx by using actions and effects.
If you want to skip the talk and start right away, head over to GitHub and look at the final code we’re going to create over the course of this and upcoming posts: https://github.com/alx-andru/draft
Setting things up 🤖
Let’s build our very minimalistic App from the ground up and dive into some of the topics. To keep things simple we’ll put zero effort in styling but remain focused on showing how things work. If you’d like to see a prettier and more functional variation, have a look at the official GitHub Repo for ng-oidc-client.
Requirements: You’ll need node.js and npm (or yarn) installed on your system
If you haven’t yet, install Angular CLI (7.0.3) using npm
npm i -g @angular/cli
Create a new Angular project with your app name, here we simply call it DraftApp.
ng new DraftApp
Install the main package ng-oidc-client
npm i -s ng-oidc-client
and it’s peer dependencies:
npm i -s oidc-client
npm i -s @ngrx/store @ngrx/effects
Now we can add the three modules StoreModule, EffectsModule and NgOidcClientModule to the AppModule in our application.
NgOidcClientModule is adding its own slice of state of NgRx following the Feature Module State Composition which is why we have to initialize StoreModule and EffectsModule with an empty forRoot.
Let’s look closer at the configuration of the NgOidcClientModule.
oidc_config: {
client_id: '...',
response_type: '...',
scope: '...',
authority: '...',
redirect_uri: '...',
post_logout_redirect_uri: '...',
silent_redirect_uri: '...',
automaticSilentRenew: true // optional
}
The ng-oidc-client library is a wrapper around oidc-client to use it in angular through services and facades in combination with state management, which is why the entire oidc-config is actually the oidc-client configuration passed through, to be used by oidc-client itself.
We also enabled the automaticSilentRenew
to ensure that our token, once obtained is automatically being renewed.
To continue, you need a working Identity Provider, here are some options: (Each of the platforms provides great documentation on their respective websites on how to create a new application, set the right CORS policies, redirect and logout urls.
IdentityServer4 — https://identityserver.io/
To continue right away with our example, we’ve already created an Identity Provider using IdentityServer4 for you to test and play around with.
You’ll find the all the resources for it on GitHub if you’d like to create one on your own: https://github.com/Fileless/ng-oidc-client-server
This implementation is solely for the purpose of testing and should not be used in production!
If you just want to learn how ng-oidc-client works, feel free to continue without any further changes of the configuration.
Auth0 — https://auth0.com/
Replace YOUR_DOMAIN
in the following listing with your Auth0 domain account and YOUR_CLIENT_ID
with the client id.
Unfortunately Auth0 is not compliant to the OpenID Connect specification for the end_session_endpoint which makes it necessary to configure a custom url endpoint in the format
https://YOUR_DOMAIN.auth0.com/v2/logout?returnTo=YOUR_CALLBACK_URL_ENCODED
To help oidc-client to resolve the rest of the required urls, you have to manually set the issuer, authorization_endpoint and userinfo_endpoint within the metadata attribute as well.
Okta — https://okta.com
Replace YOUR_DOMAIN
in the following listing with your Okta domain account and YOUR_CLIENT_ID
with the client id.
Azure Active Directory —https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app
Replace TENANT_ID
and CLIENT_ID
in the following listing with your information.
Unfortunately Microsoft has their own implementation on top of OpenID Connect to allow multi-tenancy. In addition CORS prevents the automated discovery of the necessary configuration through the .well-known/openid-configuration endpoint. Therefore you have to provide this information manually through the metadata and the signinKeys. You can discover the array of keys through following link:
https://login.microsoftonline.com/TENANT_ID/discovery/v2.0/keys
What Identity Providers are you using? If you’d like to see more examples or even an entire post, leave a comment!
As the Identity Provider will redirect back to our site to pass over the authenticated user, we have to set up a few static pages. In this example we keep the authentication, silent renew and sign-out callbacks separate from each other.
We’ll be placing our *callback.html files in a directory called src/static
DraftApp/
...
src/static
├── callback.html
├── renew-callback.html
└── signout-callback.html
...
The following example html files are opinionated and are intended to be flexible using ng-oidc-client. The minimum requirement is to import oidc-client.min.js
and invoke the appropriate callback on the UserManager e.g. invoke signinRedirectCallback()
to complete the signin via redirect.
new Oidc.UserManager().signinRedirectCallback().then(function(usr) {
log("signin response success", user);
}).catch(function(err) {log(err);});
To be able to serve our src/static
files and oidc-client
, we need to modify the angular.json
configuration for our app to include those assets.
Now the base configuration is done and we can start to implement the authentication flow in our user interface.
The First Login 🥇
To get started we’ll add a simple login button to authenticate with a redirect in the same window and display the authenticated user information as seen below:
To keep things simple we modify our already existing app.component.ts
to include the login and load the identity.
If you look at the imports, We’re using the OidcFacade
from ng-oidc-client but resolving the identity as a User
from oidc-client which is directly accessible as an Observable via the facade.
To get the user as soon as the app is loaded, we call getOidcUser()
. This will cause oidc-client to look for a stored user identity. (By default oidc-client uses the Session Storage).
To sign-in and -out we simply invoke the functions on the OidcFacade.
Before we can test everything we still need to add the buttons and the identity in our app.component.html
.
You should be able to sign-in as the user bob (or alice) with the password bob (or alice) if you used the provided IdentityServer4 example.
Now that we have the foundation of our application we can start exploring the capabilities of the library through these follow up posts:
Feel free to leave comments with any questions, feedback or ideas 🙌 …