OneLogin
SSO
Authentication
Angular
security
frontend

Integrating OneLogin with an Angular Application

by: Ashish Sharma

June 17, 2024

titleImage

Integrating OneLogin with an Angular application streamlines the authentication process and enhances security. This blog post delves into setting up the OneLogin environment, crafting essential Angular services, and implementing secure token handling with interceptors. We'll also explore how to authenticate requests on the backend.

Setting Up OneLogin

To begin with, you'll need to set up a OneLogin account and create an application to obtain the client ID and client secret.

  1. Create a OneLogin Account:
    • Go to OneLogin and sign up for an account.
  2. Create a OneLogin Application:
    • Navigate to "Apps" > "Add Apps".
    • Search for "OIDC" and select "OpenId Connect".
    • Fill in the application details.
    • In the "Configuration" tab, set the "Redirect URI" to the URL where your Angular app will handle the authorization code (e.g., http://localhost:4200/auth-callback).

Openid 3. Get Client ID and Client Secret:

  • Go to the "SSO" tab of your OneLogin application.
  • Note down the Client ID and Client Secret.

Creating the AuthService in Angular

First, let's set up the AuthService in Angular to handle login, token retrieval, and token refresh.

// auth.service.ts
import { Injectable } from '@angular/core';
// add values to env file
import { environment } from '../../../environments/environment';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, lastValueFrom } from 'rxjs';
import { Router } from '@angular/router';

interface TokenResponse {
  access_token: string;
  expires_in: number;
  id_token: string;
  token_type: string;
  refresh_token: string;
}

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  private clientId = environment.oidc_clientId;
  private clientSecret = environment.oidc_clientSecret;
  private tokenEndpoint = environment.tokenEndpoint;
  private redirectUri = environment.oidc_redirectUri;
  private userinfoEndpoint = environment.userinfoEndpoint;
  private subdomain = environment.subdomain;

  constructor(private http: HttpClient, private router: Router) {}

  login(): void {
    const loginUrl = `https://${this.subdomain}.onelogin.com/oidc/2/auth?client_id=${this.clientId}&redirect_uri=${this.redirectUri}&response_type=code&scope=openid`;
    window.location.href = loginUrl;
  }

  getToken(authorizationCode: string): Observable<TokenResponse> {
    const headers = new HttpHeaders({
      'Content-Type': 'application/x-www-form-urlencoded',
    });

    const body = new URLSearchParams();
    body.set('client_id', this.clientId);
    body.set('client_secret', this.clientSecret);
    body.set('grant_type', 'authorization_code');
    body.set('code', authorizationCode);
    body.set('redirect_uri', this.redirectUri);

    return this.http.post<TokenResponse>(this.tokenEndpoint, body.toString(), { headers });
  }

  generateRefreshToken(refreshToken: string): Observable<TokenResponse> {
    const headers = new HttpHeaders({
      'Content-Type': 'application/x-www-form-urlencoded',
    });

    const body = new URLSearchParams();
    body.set('client_id', this.clientId);
    body.set('client_secret', this.clientSecret);
    body.set('refresh_token', refreshToken);
    body.set('grant_type', 'refresh_token');

    return this.http.post<TokenResponse>(this.tokenEndpoint, body.toString(), { headers });
  }

  getUserInfo(accessToken: string): Observable<any> {
    const headers = new HttpHeaders({
      Authorization: `Bearer ${accessToken}`,
    });

    return this.http.get(this.userinfoEndpoint, { headers });
  }

  async handleAuthorizationCode(authorizationCode: string): Promise<boolean> {
    try {
      const tokenResponse = await lastValueFrom(this.getToken(authorizationCode));

      if (tokenResponse.access_token) {
        sessionStorage.setItem('access_token', tokenResponse.access_token);
        await this.fetchUserInfo(tokenResponse.access_token);
        await this.handleRefreshToken(tokenResponse.refresh_token);
        return true;
      }
      return false;
    } catch (error) {
      console.error('Error handling authorization code:', error);
      return false;
    }
  }

  async fetchUserInfo(accessToken: string): Promise<void> {
    try {
      const userInfo = await lastValueFrom(this.getUserInfo(accessToken));
      // Store user info if needed
    } catch (error) {
      console.error('Error fetching user info:', error);
    }
  }

  async handleRefreshToken(refreshToken: string): Promise<void> {
    try {
      const refreshTokenResponse = await lastValueFrom(this.generateRefreshToken(refreshToken));
      sessionStorage.setItem('access_token', refreshTokenResponse.access_token);
      sessionStorage.setItem('refresh_token', refreshTokenResponse.refresh_token);
      console.log('Token refreshed successfully.');
    } catch (error) {
      console.error('Error refreshing token:', error);
      this.router.navigate(['/auth']);
    }
  }
}

Handling Token Refresh

To ensure tokens are refreshed periodically, we need to set up a TokenRefreshService.

// token-refresh.service.ts
import { Injectable } from '@angular/core';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root',
})
export class TokenRefreshService {
  private scheduleTokenTimer: number = 120000; // 120,000 milliseconds = 2 minutes

  constructor(private authService: AuthService, private router: Router) {
    this.scheduleTokenRefresh();
  }

  private async handleRefreshToken(): Promise<void> {
    const refreshToken = sessionStorage.getItem('refresh_token');
    if (refreshToken) {
      try {
        await this.authService.handleRefreshToken(refreshToken);
      } catch (error) {
        console.error('Error refreshing token:', error);
        this.router.navigate(['/auth']);
      }
    } else {
      console.log('No refresh token found in session storage.');
    }
  }

  private scheduleTokenRefresh(): void {
    setInterval(() => {
      this.handleRefreshToken();
    }, this.scheduleTokenTimer);
  }
}

Adding an Interceptor to Angular

To ensure all HTTP requests include the access token, we need to add an HTTP interceptor.

// token.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { Router } from '@angular/router';

export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  const authToken = sessionStorage.getItem('access_token');

  if (authToken) {
    req = req.clone({
      setHeaders: {
        Authorization: `Bearer ${authToken}`,
      },
    });
  }

  return next(req).pipe(
    catchError((error) => {
      if (error.status === 401) {
        router.navigate(['/auth']);
      }
      return throwError(error);
    })
  );
};

Configuring Angular to Use the Interceptor

// app.config.ts
import { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { tokenInterceptor } from './token.interceptor';
import { TokenRefreshService } from './token-refresh.service';

export function initializeApp(tokenRefreshService: TokenRefreshService) {
  return (): void => {
    tokenRefreshService.handleRefreshToken();
  };
}

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideAnimationsAsync(),
    provideHttpClient(
      withInterceptors([tokenInterceptor])
    ),
    TokenRefreshService,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeApp,
      deps: [TokenRefreshService],
      multi: true,
    },
  ],
};

Note: Ensure that the application is set to POST, not BASIC

Authenticating in the Backend

To authenticate in the backend, you can follow the OneLogin developer documentation on how to verify tokens:

OneLogin Backend Authentication Documentation

This documentation provides detailed steps on how to validate the JWT tokens received from OneLogin.

Conclusion

By following these steps, you can securely integrate OneLogin with your Angular application. The AuthService handles login and token retrieval, while the TokenRefreshService ensures tokens are refreshed periodically. Adding an HTTP interceptor ensures all requests include the access token, maintaining secure communication with your backend.

contact us

Get started now

Get a quote for your project.
logofooter
title_logo

USA

Edstem Technologies LLC
254 Chapman Rd, Ste 208 #14734
Newark, Delaware 19702 US

INDIA

Edstem Technologies Pvt Ltd
Office No-2B-1, Second Floor
Jyothirmaya, Infopark Phase II
Ernakulam, Kerala 682303

© 2024 — Edstem All Rights Reserved

Privacy PolicyTerms of Use