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
- Tutorial Source Code – https://github.com/nickalcala/angular-oauth2-example
- Express OAuth 2 Server Source Code – https://github.com/nickalcala/node-express-oauth2-server-example
Lovan
July 24, 2020 at 10:35 am
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.
Nick Alcala
July 24, 2020 at 12:21 pm
The button used goToLoginPage() :).
<button class="btn btn-primary" (click)="goToLoginPage()">Login</button>