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

Angular2 app fails to update after emitting an event

I need help with a child component responsible for updating phone numbers on a webpage. The goal is for the application to automatically display the changed phone number once the user hits the 'save' button. Here's a visual of how the appli ...

ngOnChange event not firing despite attempting various solutions

My goal is to utilize ngOnChanges to update a string (even or odd) after another variable (counter) changes. However, I am encountering an issue where the value of the message remains blank and does not update when the counter changes. export class Counter ...

Getting into nested information in a JSON string using TypeScript

I need help accessing the data values (data1, data2, and date) from this JSON structure. I would like to store these values in an array that can be sorted by date: { "07" : { "07" : { "data1" : "-1", "data2" : "test", "date" : "1995-07-07" ...

Persistent Ionic tabs after user logs out - keeping tabs active post-logout

My Ionic app features tabs and authentication. While the authentication process works perfectly, I am facing an issue with the tabs still displaying after logging out. Below is my login method: this.authProvider.loginUser(email, password).then( auth ...

What is the best approach for designing a UI in Angular to showcase a matrix of m by n dimensions, and how should the JSON format

click here for a sneak peek of the image Imagine a matrix with dimensions m by n, containing names on both the left and top sides. Remember, each column and row must be labeled accordingly. ...

I am encountering an issue with an undefined variable called "stripe" in my Angular project, despite the fact

Within my stripecreditcardcomponent.html file: <script src="https://js.stripe.com/v3/"></script> <script type="text/javascript"> const payment = Stripe('removed secret key'); const formElements = paymen ...

Creating a personalized state object containing unresolved promises in React Native utilizing axios inside a custom React Hook

I'm currently in the process of creating a custom state within a custom Hook for React Native (non-Expo environment). The state I am working on looks like this: interface ResponseState { api1: { error: boolean; errorMsg?: string; ...

Transferring information between AngularJS and Angular applications

Having two applications on localhost: http://localhost/testsite (Angular js app) http://localhost:4200 (Angular app) Seeking assistance in sharing data from Angular JS to Angular application. Any guidance would be appreciated. Thank you. ...

Is it feasible to bring in a Typescript file into an active ts-node REPL session?

I want to experiment with some Typescript code that I have written. Currently, I usually run ts-node my-file-name.ts to test it out. But I am interested in making this process more interactive, similar to the Python REPL where you can import modules and ...

Activate the datepicker in Angular by clicking on the input field

This is my html file: <mat-form-field> <input matInput [matDatepicker]="picker" placeholder="Choose a date"> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-date ...

Is it possible to toggle between namespace and class using parentheses?

While working with older javascript code, I stumbled upon the following snippet: // module1.js class Class { constructor() { console.log('hello') } } const exported = { Class: Class, } module.exports = exported This code is then ...

Using Nestjs to inject providers into new instances of objects created using the "new" keyword

Is it possible to inject a provider into objects created by using the new keyword? For instance: @Injectable() export class SomeService { } export class SomeObject { @Inject() service: SomeService; } let obj = new SomeObject(); When I try this in my t ...

When a function is passed as an argument in Typescript, it may return the window object instead of the constructor

I'm still getting the hang of typescript, and I've come across a situation where a function inside a Class constructor is calling another function, but when trying to access this within sayHelloAgain(), it returns the window object instead. With ...

Encountering TypeScript error 2345 when attempting to redefine a method on an Object Property

This question is related to Object Property method and having good inference of the function in TypeScript Fortunately, the code provided by @jcalz is working fine; const P = <T,>(x: T) => ({ "foo": <U,>(R: (x: T) => U) => ...

Bug in auto compilation in Typescript within the Visual Studios 2015

Currently, I am utilizing Visual Studio Pro 2015 with auto compile enabled on save feature. The issue arises in the compiled js file when an error occurs within the typescript __extends function. Specifically, it states 'Cannot read property prototyp ...

Error parsing Angular2 with ngFor directive

I'm attempting to utilize the ngFor directive in an angular 2 (alpha 35) project, but I keep encountering the error EXCEPTION: Can't bind to 'ngforOf' since it isn't a known property of the '' element and there are no mat ...

Encountered an issue during installation: Error message states that Typings command was not

I've encountered permission errors with npm, so I decided to reinstall it. However, I'm facing an issue with the 'typings' part where it displays a 'typings: command not found' error. This problem seems to be related to Angula ...

Different methods to prompt TypeScript to deduce the type

Consider the following code snippet: function Foo(num: number) { switch (num) { case 0: return { type: "Quz", str: 'string', } as const; case 1: return { type: "Bar", 1: 'value' } as const; default: thr ...

Tips on preventing pooling in Angular 5

service.ts: // Fetch all AgentLog logs using pooling method getAgentLogStream(): Promise<string> { const url = `${this.testCaseUrl}/logfile`; return Observable .interval(5000) .flatMap((i)=> this.http.get(url).toPromise().then(respons ...

Angular: Showing the Gap between Basic Elements within an Array in the Template

I've been working on displaying a nested JSON object in a table, where there is an array of strings like obj.someKey = ['a','b','c']. Currently, I am directly showing the array content in the td tag of the table. <td ...