Authentication using OAuth2 + OpenId Connect in Angular Single Page Application

Authentication using OAuth2 + OpenId Connect in Angular Single Page Application

Learn how to use Okta as an authorization server for your angular application

Authentication and authorization are critical component of any application. In simple terms, authentication is a mechanism to verify if the user is the person who he/she is claiming to be. On the other hand, authorization is used to check for the accesses the user have. Authorization tells the application what actions the user is allowed to perform. In this article, we will focus on authentication only. Authorization will be covered in next articles of the series.

If you want your users to login to your application using Google, Facebook, GitHub or any other identity providers, then you need to use OAuth2 for authorization.

OAuth2 and OpenId Connect

OAuth2 is an industry-standard protocol for authorization. It is not meant for authentication. But, we need authentication as well. That's where OpenId Connect comes into play. It is a simple identity layer on top of OAuth2 protocol.

OAuth2 Components

3 components are involved while making single page application which uses OAuth2.

  1. Authorization Server takes care of authenticating and authorizing users using various methods - Username/Password, Google, Facebook, GitHub (you name it). We have some options for it. First is to build your own which will be very time and resources consuming, error-prone and not so secure task. Second is to use any existing authorization server and maintain that by yourself. Keycloak is a good example of it. Third option is to completely outsource this task to other identity providers. Okta and Auth0 are good examples of these providers. We will be using Okta for this series.

  2. Client Server is responsible to display User Interface and data in the web browser. For now, we will use Angular framework to create it.

  3. API/Resource Server exposes APIs to be consumed by Client Server and is responsible for storing all the data related to the application. It also restricts the user from accessing the resources which she doesn't have permission to. We will use Spring Boot to create API Server in next parts of the series.

Setup Okta

Let's create one application and user in Okta identity platform for this application.

Create Application

  1. Create a free developer account at https://developer.okta.com/.
  2. Go to Applications > Applications.
  3. Click on Create App Integration.
  4. For Sign-in method, select OIDC - OpenId Connect.
  5. For Application type, select Single-Page Application.

Screenshot from 2021-12-02 22-42-25.png

  1. Click on Next.
  2. For App Integration name, enter name best suitable to your application.
  3. For Grant Type, choose Authorization Code.
  4. For Sign-in redirect URIs, add localhost:4200.
  5. For Sign-out redirect URIs, add localhost:4200.
  6. For Controlled Access, select Allow everyone in your organization.
  7. Click on Save.

After that, we can see application name, Client ID, Okta domain and other settings for the newly created application. Client ID and Okta domain will be used later in the article.

Create User

  1. Go to Directory > People.
  2. Click on Add Person.
  3. Fill all the fields as given in the image below and click on Save.

Screenshot from 2021-12-02 23-53-17.png

Build Angular application

Now, let's dive into code and build an angular application which will use Okta as an identity provider to login the users. But hold on, let's make sure you have the right tools with you.

  1. Node.js is a JavaScript runtime built on Chrome's V8 JavaScript Engine. It is needed to run the JavaScript code outside of the browser. If you don't have it in your machine, install it from here.
  2. Angular CLI is used to create angular apps. If you don't have it, follow the instructions here to install it.

Now, my friend, you are ready to code.

Open terminal and run the following command to create a new angular app named AngularOAuth2Demo:

$: ng new AngularOAuth2Demo

This command might prompt you to select some options. Choose what is best for your application. After that, change your current directory to AngularOAuth2Demo app with the following command:

$: cd AngularOAuth2Demo

To manage all the requests for OAuth2 flow and to manage state of the logged in user in our angular app, we will use angular-oauth2-oidc package. Install it into your app using the following command:

$: npm i angular-oauth2-oidc --save

Crete a file src/app/config/authCodeFlowConfig.ts to manage configurations related to OAuth2 flow. You will need to create config directory inside src/app directory and then create the file. Replace the code of authCodeFlowConfig.ts with the following code:

import { AuthConfig } from "angular-oauth2-oidc";

export const authCodeFlowConfig: AuthConfig = {
  issuer: "<Your Issuer URI>",
  redirectUri: window.location.origin,
  clientId: "<Your Client Id>",
  responseType: "code",
  scope: "openid profile email",
  showDebugInformation: true
};

Let's break down this file and try to understand it. Issuer URI is a unique URI to identify the application for authentication and authorization purpose. You can find it on your application page in identity provider. If you created app in Okta, you will see Okta Domain for you app and the Issuer URI will be https://<Your Okta Domain>/oauth2/default.

The second part is redirectUri, which is used to redirect the user after authentication. This URI must be one of those Sign-in redirect URIs which you configured while making the app in Okta provider. We are using window.location.origin which will be http://localhost:4200, when the angular app will start, since Angular apps run on port 4200 by default. But, if you deploy your app to any server, say https://yourcoolwebsite.dev, then be sure to add this URI (https://yourcoolwebsite.dev) to Sign-in redirect URIs for your app in Okta or any other identity provider.

The clientId is unique to your application and you will find it on the application page in your identity provider. We are using responseType as "code", so that identity provider will not send the access token and id token in query parameters (which is displayed in browser search bar). The next attribute scope is used to get access token with the given scopes from identity provider. The last attribute showDebugInformation is used to print some debugging information to the console.

Replace the code of app.module.ts with the following code:

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { OAuthModule } from 'angular-oauth2-oidc';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    OAuthModule.forRoot()
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Here, we imported HttpClientModule module from '@angular/common/httppackage andOAuthModulefromangular-oauth2-oidc` package to handle auth flow and user state.

Now, replace the code of app.component.ts with the following code:

import { Component } from '@angular/core';
import { OAuthService } from 'angular-oauth2-oidc';
import { authCodeFlowConfig } from './config/authCodeFlowConfig';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'AngularOAuth2Demo';

  constructor(private oauthService: OAuthService) {
    this.oauthService.configure(authCodeFlowConfig);
    this.oauthService.loadDiscoveryDocumentAndTryLogin();
  }

  login() {
    this.oauthService.initCodeFlow();
  }

  logout() {
    this.oauthService.logOut();
  }

  get name () {
    const claims = this.oauthService.getIdentityClaims();
    if (!claims) return null;
    const nameClaim = claims as {name: string};
    return nameClaim.name;
  }
}

Here, in the constructor we are configuring the oauthService to use authCodeFlowConfig.ts file for configuration. Then, we are trying to login the user if the session already exists.

In the login method, we are initiating the code flow for login. The initCodeFlow method of oauthService handles all the requests to and from the identity provider to get the access token. In the logout method, we are simply logging the user out.

The name setter method is used to extract name property from the claims, which we receive from the identity provider.

Now, replace the code of app.component.html with the following code:

<div>
  Welcome to Angular OAuth2 Demo

  <div *ngIf="name">
    Welcome, {{name}}<br />
    <button (click)="logout()">Logout</button>
  </div>
  <div *ngIf="!name">
    You are not logged in.
    <button (click)="login()">Login</button>
  </div>
</div>

Here, if the name is not empty, we are displaying a welcome message and a button which will call logout method when clicked. If the name is null or empty, another button will be displayed, which will call login method when clicked. And we have already created login and logout methods and name setter method in app.component.ts.

Now, its time to see the application in action. Run the following command in the terminal to start the application:

$: ng serve

In a private window of the browser, open http://localhost:4200. You will see a page with Login button. Click on login button. You will be redirected to Okta login page to enter username and password. Enter the username and password of the user which you created in Okta platform. After log in you will be redirected back to the angular application. Now, you will be able to see welcome message along with Logout button. Clicking on the logout button will logout the user and login button will appear again.

Summary

Click here to view the source code on GitHub. In this article, we used Okta as an identity provider to user OAuth2 in a Single Page Application. We still need to make resource server in order to complete the application. We will do that in next article. Also, we will discuss about some of the drawback of client side app and how to resolve them.

Your suggestions are welcome. Until then, Bye and keep learning...