30 Nov 2019
cat: Angular 2+, API
2 Comments

Angular 8 OAuth2 Authorization Code Flow

Introduction

In this tutorial we will create an Angular application that authenticates to an OAuth2 server with Authorization Code flow. The app will redirect to the OAuth2 server’s login page then  redirected back to the app after login. This tutorial should work on Angular 2 or above.

We will use the Express OAuth2 server from the previous tutorial which you can get here.

Setup

Assuming you already have an Angular repository ready, we will need a component for redirecting to the OAuth server and for callback.

We will just make one component to make it simple.

ng g component Login

For the routing, we will need one to show Login button and one for callback.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login/login.component';


const routes: Routes = [
    { path: 'login', component: LoginComponent },
    { path: 'oauth/callback', component: LoginComponent },
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

I also added bootstrap on the index.html to add some styling.

Redirecting to OAuth Server

We can create a method on the new component to redirect to the OAuthServer. The URL contains query parameters response_type, state, client_id, scope, and redirect_uri. We also need to make sure redirect_uri is URL encoded. The parameter state can be random string.

src\app\login\login.component.ts

import { Component, OnInit } from '@angular/core';

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

    goToLoginPage() {
        const params = [
            'response_type=code',
            'state=1234',
            'client_id=api',
            'scope=example',
            encodeURIComponent('redirect_uri=http://localhost:4200/oauth/callback'),
        ];

        window.location.href = 'http://192.168.10.10:3000/oauth/authenticate?' + params.join('&');
    }

}

src\app\login\login.component.html

<button class="btn btn-primary" (click)="goToLoginPage()">Login</button>

When clicking the Login button will redirect to the OAuth server.

After logging in to the OAuth server, we will be redirected to the callback URL with some query string. Here is an example.

http://localhost:4200/oauth/callback?code=c35ed78b440eeee3d24af8eda4d56e0693dc1ccc&state=1234

 

Callback URL

In the previous section we set /oauth/callback as our callback URL and pointed it to LoginComponent. First is to get the query parameter code from the URL and exchange it with an access token.

We will use the ActivatedRoute for the query parameter and HttpClient to make AJAX requests.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient, HttpParams } from '@angular/common/http';

@Component({
    selector: 'app-login',
    templateUrl: './login.component.html',
})
export class LoginComponent implements OnInit {

    oauthResponse: any;

    constructor(
        private activatedRoute: ActivatedRoute,
        private http: HttpClient
    ) {
    }

    ngOnInit() {
        this.activatedRoute.queryParams.subscribe(params => {
            if (params.code) {
                this.getAccessToken(params.code);
            }
        });
    }

    getAccessToken(code: string) {

        const payload = new HttpParams()
            .append('grant_type', 'authorization_code')
            .append('code', code)
            .append('redirect_uri', 'http://localhost:4200/oauth/callback')
            .append('client_id', 'api');

        this.http.post('http://192.168.10.10:3000/oauth/access_token', payload, {
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).subscribe(response => {
            this.oauthResponse = response;
        });
    }

    goToLoginPage() {
        // omitted for brevity
    }
}

As you can see, we used form data instead of JSON as content type as mandated by the OAuth2 specification.

We also have a new property called oauthResponse where we store the access token, refresh token, and others. You can also store this on the browser’s local storage to use on other requests.

On our template file we can show the response.

src\app\login\login.component.html

<button class="btn btn-primary"  (click)="goToLoginPage()">Login</button>
<pre><code>{{ oauthResponse | json }}</code></pre>

Trying it out

Now to test this, we will need to do a request to a secured route where access token is required. On our sample Express OAuth2 server we have an example of such route.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { HttpClient, HttpParams } from '@angular/common/http';

@Component({
    selector: 'app-login',
    templateUrl: './login.component.html',
})
export class LoginComponent implements OnInit {

    oauthResponse: any;
    account: any;

    // omitted for brevity

    getProfile() {
        this.http.get('http://192.168.10.10:3000/account', {
            headers: {
                Authorization: 'Bearer ' + this.oauthResponse.access_token
            }
        }).subscribe(response => {
            this.account = response;
        });
    }

}

Here we have a new property called account and getProfile. Next, is on our HTML we will show a button and a section to show the values.

This is the final src\app\login\login.component.html.

<button class="btn btn-primary"
        (click)="goToLoginPage()">Login</button>
<pre><code>{{ oauthResponse | json }}</code></pre>
<button class="btn btn-primary"
        *ngIf="oauthResponse"
        (click)="getProfile()">Profile</button>
<div *ngIf="account">
    <p><strong>First Name: </strong>{{ account.firstName }}</p>
    <p><strong>Last Name: </strong>{{ account.lastName }}</p>
    <p><strong>Email: </strong>{{ account.email }}</p>
    <p><strong>Username: </strong>{{ account.username }}</p>
</div>

That is all for now. Thanks for reading!

References


  1. I am trying to implement the same solution in an Angular 8 app. I followed all the steps mentioned here. But still it is not working, can you suggest what might be the reason. Only thing is I do not have a login button to start the process I am calling the goToProfile() method in the constructor itself.


    • The button used goToLoginPage() :).
      <button class="btn btn-primary" (click)="goToLoginPage()">Login</button>

Post A Comment