Issue with obtaining access token in Angular 8 authentication flow with Code Flow

As I work on implementing SSO login in my code, I encounter a recurring issue. Within my app.module.ts, there is an auth.service provided inside an app initializer. Upon hitting the necessary service and capturing the code from the URL, I proceed to send a POST request containing a body that should return the access token. However, upon making the post request, the application refreshes and the process repeats itself. After several attempts, it terminates with a message stating that the Authorization code has expired.

Inside app.module.ts file:
export function appsso(http: HttpClient, authService: AuthService) {
    return (): Promise<any> => {
        return authService.init()
    };
}

{provide: APP_INITIALIZER, useFactory: appsso, deps: [HttpClient, AuthService], multi: true}

Inside authService file:

constructor(private http: HttpClient) {}

init() {
    return new Promise<void>((resolve, reject) => {
        console.log('init is called');
        if (localStorage.getItem('token')) {              
            console.log('good to go');
        } else {
            if (!location.href.includes('?code=')) {        
                const params = [
                    'response_type=code',
                    'client_id=abcde',
                    'scope=openid profile',
                    'redirect_uri=https://URLhere/a.0/',
                ];
                location.href = 'https://secureURLhere/authorize?' + params.join('&');      
                return 
            } else {
                const code = window.location.href.split('=')[1];     
                return this.getAuthToken(code).then(data): any => {
                    localStorage.setItem('tokenData', data);
                    console.log('access token received');
                    resolve();
                }, error ((err) => {
                    console.log ('error occured');
                    reject();
                });    
            }
        }
    }

getAuthToken(code: string){
  let body: HttpParams = new HttpParams();
  body = body.append('grant_type', 'authorization_code')
  body = body.append('code', code)
  body = body.append('client_id', 'abcde')
  body = body.append('client_secret', '12345');
  return this.http.post('https://secureURLhere/token', body).toPromise();   
}

Additionally, the header is set to

'Content-Type': 'application/x-www-form-urlencoded'
. Despite setting up for receiving the access token when the Post API is hit, the application continues to refresh. How can this persistent issue be resolved?

Answer №1

There are several issues in the code that need to be addressed. It is important to ensure that every if statement returns something at the end to prevent the code from getting stuck. Additionally, incorporating returns within if statements can help avoid unnecessary nesting and make the code more readable.

The structure of your service code should resemble the following:

import { HttpClient, HttpParams } from '@angular/common/http';

export interface AuthTokenResponse {
  access_token: string;
  id_token: string;
  expire_in: number;
  token_type: string;
}

class AuthService {
  constructor(private http: HttpClient) {}

  init() {
    return new Promise<void>((resolve, reject) => {
      // Check for token in local storage
      if (localStorage.getItem('auth-token')) {
        resolve(); // Token exists, proceed
        return;
      }

      const params = window.location.search
        .substr(1)
        .split('&')
        .reduce((prev, current) => {
          const [key, value] = current.split('=');
          if (key || (value !== undefined && value !== null)) {
            prev[key] = value;
          }
          return prev;
        }, {});

      if (params && params['code']) {
        this.getAuthToken(params['code']).then(
          (data) => {
            localStorage.setItem('auth-token', JSON.stringify(data));
            resolve(); // Save token to local storage
          },
          (err) => {
            reject(); // Error handling
          }
        );
        return;
      }

      const redirectParams = new HttpParams()
        .set('response_type', 'code')
        .set('client_id', 'abcde')
        .set('scope', 'openid profile')
        .set('redirect_uri', 'https://URLhere/a.0/');

      location.href =
        'https://secureURLhere/authorize?' + redirectParams.toString();
      reject(); // Redirect if no token present
    });
  }

  getAuthToken(code: string): Promise<AuthTokenResponse> {
    const body = new HttpParams()
      .append('grant_type', 'authorization_code')
      .append('code', code)
      .append('client_id', 'abcde')
      .append('client_secret', '12345');

    return this.http
      .post<AuthTokenResponse>('https://secureURLhere/token', body)
      .toPromise(); // Make API call to fetch auth token
  }
}

Answer №2

Revision 1: Do you remember to ensure the promise is returned within the getAuthToken() method? It seems like when you call getAuthToken, it returns null.

Revision 2: I don't see any issue with http.post that could trigger a refresh of the app. The only line that could result in a reload is this:

 location.href = 'https://secureURLhere/authorize?' + params.join('&'); 

I suspect the problem lies within the if condition here:

location.href.includes('?code=')

You may need to review that section in getAuthToken when setting the params. It seems like you might be receiving &code= instead of the expected ?code=. I could be mistaken.

Revision 3

Based on the code, my assumption is that the POST request follows this structure:

POST https://secureURLhere/token HTTP1.1
(Body content below)
grant_type=authorization_code&code=code&client_id=abcde&client_secret=12345

You can verify this in the Network tab of Chrome DevTools. Show it to your API contact or refer to the API documentation for clarification on the expected format. Regarding user-defined headers, they should be HttpHeaders type and included as the third parameter of http.post() within an object named "headers". The default headers are set by the HttpClient automatically.

When it comes to how it operates, http.post returns an observable that you can subscribe to. In this scenario, you are converting the observable into a promise and handling it further. As for the type of observable being used – it depends on the response sent by the API. It could be an observable of String (Observable), Blob (Observable), or full HttpResponse (Observable<HttpResponse>) among others. There are 15 different overloads of http.post. You specify the desired response reading method either by setting responseType during the http.post request creation or using generics and typecasting. Refer to the official documentation for more clarity.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

typescript exploring the versatility of dynamic types and generics

Understanding TypeScript dynamic and generic types can be challenging for me. The goal is to create a function that generates an object with a specific type, where some properties of the object must match the parameters provided to the function. Essentia ...

The onChange() function in the Date Picker event is failing to activate

My issue involves the Angular Material DatePicker. Everything seems to be working smoothly, however, the (change) event does not trigger when I change the date using the calendar. Manually inputting a date works just fine. I would like to display an erro ...

What is the best way to reference typescript files without using absolute paths?

As typescript does not seem to have built-in support for absolute path references (according to this GitHub issue), it becomes difficult to organize and maintain my file references. With TypeScript files scattered across various locations in my folder stru ...

Utilizing BehaviourSubject for cross-component communication in Angular

My table is populated with data from a nodeJS API connected to methods inside my service. I attempted using a Behavior Subject in my service, initialized as undefined due to the backend data retrieval: Service: import { Injectable } from "@angular/core" ...

"Trying to create a new project with 'ng new xxx' command resulted in error code -4048

Whenever I execute 'ng new projectName' in vs code, I encounter the following issue. ng new VirtualScroll ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? SCSS [ http://sass-lang.com ] CREATE Vir ...

Angular 2: Enhancing User Experience with Pop-up Dialogs

Looking to implement a popup dialog that requests user input and returns the value. The popup component is included in the root component, positioned above the app's router outlet. Within the popup component, there is an open() method that toggles a ...

The async pipe value seems to be constantly null when dealing with router events

I am facing a straightforward problem while attempting to access an asynchronous property in my template - the returned value is consistently null. This is the method I am using: someAsyncProperty():Observable<string> { return this._router.event ...

Exploring the capabilities of argon2-browser in a cutting-edge setup with vite

After spending several hours attempting to implement the argon2-browser library in a Vue app with Vite, I have been encountering a persistent error. Despite following the documentation closely, I keep receiving the following message: This require call is ...

Establish a connection between MongoDB and the built-in API in Next.js

I've been working on integrating a MongoDB database with the Next.js built-in API by using the code snippet below, which I found online. /api/blogs/[slug].ts import type { NextApiRequest, NextApiResponse } from 'next' import { connectToData ...

Having trouble getting web components registered when testing Lit Element (lit-element) with @web/test-runner and @open-wc/testing-helpers?

Currently, I am working with Lit Element and Typescript for my project. Here are the dependencies for my tests: "@esm-bundle/chai": "^4.3.4-fix.0", "@open-wc/chai-dom-equals": "^0.12.36", "@open-wc/testing-help ...

Setting an expiry date for Firestore documents

Is it feasible to set a future date and time in a firestore document and trigger a function when that deadline is reached? Let's say, today I create a document and specify a date for the published field to be set to false fifteen days later. Can this ...

Maintain the text layout when copying and pasting from the Angular Application

After noticing that copying and pasting text from an Angular Application to a text editor like Microsoft Word results in losing the original format, I decided to investigate further. An example I used was the angular material website: https://material.ang ...

Issue encountered while generating a dynamic listing using Angular

My goal is to generate a dynamic table using Angular. The idea is to create a function where the user inputs the number of rows and columns, and based on those values, a table will be created with the specified rows and columns. However, I am facing an iss ...

How can the panel within an accordion be enlarged or minimized?

Currently, I am implementing an accordion feature with the option to expand or collapse all panels using two buttons. My goal is to allow users to manage each panel within the accordion individually. However, I have encountered an issue that needs attenti ...

Guidelines for transitioning an AngularJS module for injection into an Angular 2 component

After diving into the Angular 2 upgrade guide and successfully setting up a hybrid app (combining ng1 as a base code with components and services gradually transitioning to ng2), I've hit a snag. How do I incorporate 3rd party ng1 modules so that Angu ...

The process of exporting a singleton instance

I have created a new class called AppViewModel with a setting property set to 1: class AppViewModel { setting: number = 1; } export = AppViewModel; Afterward, I imported the class and instantiated it within another class named OrderEntry: import AppV ...

What is the best way to initiate the registration page through the @auth0/auth0-react library?

I've hit a roadblock in trying to automatically launch the sign-up (registration) page using @auth0/auth0-react. Previously, I would send mode which worked with auth0-js. So far, I have attempted the following without success: const { loginWithRedir ...

Restrict the number of rows in a CSS grid

Hello there, I am in the process of creating an image gallery using CSS grid. Specifically, my goal is to initially show just one row of images and then allow users to click a "Show more" button to reveal additional images. You can see my progress here: ...

Is there a way for me to store the current router in a state for later use

I am currently working on implementing conditional styling with 2 different headers. My goal is to save the current router page into a state. Here's my code snippet: const [page, setPage] = useState("black"); const data = { page, setPage, ...

(Chrome Extension, Angular 7) How come a mat dialog displays properly on a webpage but fails to render in a chrome extension? (Including a repo for reproduction)

I am facing an issue with my Angular 7 code that uses material dialogs. While everything works fine when viewed as a webpage, the problem arises when I try to load it as a chrome extension. The dialogs render incorrectly and make the entire extension windo ...