Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I have issue with refreshtoken , Two requests at the same time #200

Open
Moataz01210049831 opened this issue Mar 29, 2023 · 7 comments
Open

Comments

@Moataz01210049831
Copy link

Two requests happened for getting refresh token first one success but second failed and log me out
it's failed due to taking the same exact refresh token in payload not new one
here my interciptor code

/* eslint-disable @typescript-eslint/naming-convention */
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { finalize } from 'rxjs/operators';
import { environment } from 'src/environments/environment';

import { TokenResponse, Requestor } from '@openid/appauth';
import { AuthService } from 'ionic-appauth';
import { from, Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private secureRoutes = environment.secureExternalApis;
private secureExternalRoutes = environment.secureExternalRoutes;
private guestToken = environment.guestToken;
constructor(private authService: AuthService, private appState: Store,
public store: Store,

) { }

private onEnd(): void {
console.log("we end here ")
}

intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent> {
// convert promise to observable using 'from' operator
return from(this.handle(req, next))
}

async handle(request: HttpRequest, next: HttpHandler):
Promise<HttpEvent> {
const lang = localStorage.getItem('culture') ?? 'ar';
if (
!this.secureRoutes.find((x) => request.url.startsWith(x))
|| this.guestToken.find((x) => request.url.includes(x))
// && request.withCredentials !== true
) {
if (this.secureExternalRoutes.find((x) => request.url.includes(x))) {

    const token: TokenResponse = await this.authService.getValidToken();
    // debugger;
    request = request.clone({
      setHeaders: {
        // authorization: `Bearer ${t}`,
        authorization: `${(token.tokenType === 'bearer') ? 'Bearer' : token.tokenType} ${token.accessToken}`,
        'Accept-Language': lang,
      },
    });
    return next.handle(request).toPromise();;

  }
  else if (this.guestToken.find((x) => request.url.includes(x))) {
    // let token: TokenResponse = await this.getToken();

    // if (token && token.accessToken) {
    //   request = request.clone({
    //     setHeaders: {
    //       authorization: `${(token.tokenType === 'bearer') ? 'Bearer' : token.tokenType} ${token.accessToken}`,
    //       'Accept-Language': lang,
    //     },
    //   });
    // }
    // else {
    let guestUserToken: any = JSON.parse(localStorage.getItem('guestUserToken'));
    request = request.clone({
      setHeaders: {
        authorization: `Bearer ${guestUserToken}`,
        //  authorization: `${(guestUserToken.token_type === 'bearer') ? 'Bearer' : guestUserToken.token_type} ${guestUserToken.access_token}`,
        'Accept-Language': lang,
      },
    });
    //}



    return next.handle(request).toPromise();;

  }

  else {
    request = request.clone({
      setHeaders: {
        'Accept-Language': lang,
      },
    });
    return next.handle(request).toPromise();;

  }
}

let token: TokenResponse = await this.getToken();

if (!token) {
  request = request.clone({
    setHeaders: {
      'Accept-Language': lang,
    },
  });
  return next.handle(request).pipe().toPromise();;
}
request = request.clone({
  setHeaders: {
    // authorization: `Bearer ${token}`,
    authorization: `${(token.tokenType === 'bearer') ? 'Bearer' : token.tokenType} ${token.accessToken}`,
    'Accept-Language': lang,
  },
});

return next.handle(request).pipe().toPromise();

}

async getToken() {
let tokenResponse: TokenResponse = await this.authService.getValidToken(10);

return tokenResponse;

}

getRequest(request: HttpRequest, lang, token) {
if (token && token.accessToken) {
request = request.clone({
setHeaders: {
authorization: ${(token.tokenType === 'bearer') ? 'Bearer' : token.tokenType} ${token.accessToken},
'Accept-Language': lang,
},
});
}
else {
request = request.clone({
setHeaders: {
'Accept-Language': lang,
},
});
}
}

}

@Moataz01210049831 Moataz01210049831 changed the title Hello I have issue with refreshtoken , I have issue with refreshtoken , Two requests at the same time Mar 29, 2023
@tomek14
Copy link
Contributor

tomek14 commented Apr 27, 2023

Hi,
I am also having the same problem - when trying to refresh token I got 2 calls, one of them is failing because the old refresh token gets revoked by the call that will first reach Token Endpoint. It doesn't matter if the access token was expired or not, I just called refreshToken() method from to check it out.

One thing that I noticed is that when I am using Android Emulator with Pixel 4 and Android 11, everything works fine, but when I am using Galaxy S21 with Android 13 or Nokia T20 Tablet with Android 12 the problem occurs. I don't have an iPhone or Mac to check it out.

Strange thing because everything was running fine until now, maybe this is a problem with Android update.

BTW. I am using Angular 13 with Ionic 6 and Capacitor 4.8. I also upgraded ionic-appauth to 2.0.0 to check if something changed, but no success.

@Moataz01210049831
Copy link
Author

@tomek14
I edited in service itself trying to run it only once and it worked fine, but if i closed app and opened again user have to login again

@richardkshergold
Copy link

@Moataz01210049831 I'm a bit confused by what you are doing - what is your guestToken ? In my httpinterceptor all I do is call the library's getValldToken method and use the token that comes back ? As far as I'm concerned the library should take care of everything else. Are you overcomplicating things or am I under-complicating things?

@Moataz01210049831
Copy link
Author

Yes you are right it's suppose to solve everything , but when i didn't found any response to my problem i just add a small change in refreshToken function , to make it work for one time only

@richardkshergold
Copy link

@Moataz01210049831 ok thanks - what small change did you make?

@Moataz01210049831
Copy link
Author

public async refreshToken() {
if(localStorage.getItem('refreshToken')!==null){
return null;
}
localStorage.setItem('refreshToken', new Date().toLocaleTimeString());
await this.requestTokenRefresh().then(()=>{
localStorage.removeItem('refreshToken');
}).catch((response) => {

        this.storage.removeItem(TOKEN_RESPONSE_KEY);
        
        this.notifyActionListers(AuthActionBuilder.RefreshFailed(response));
    });
}

i did this in auth-service.ts

@tomek14
Copy link
Contributor

tomek14 commented May 7, 2023

I think I solved the doubled TokenRefresh request issue. It seems that when the application uses newer WebViews, the deprecated .toPromise() method from RxJs works incorrectly. I solved it by applying fixes from RxJS docs https://rxjs.dev/deprecations/to-promise:

import { Injectable } from '@angular/core';
import { Requestor } from '@openid/appauth';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { XhrSettings } from 'ionic-appauth/lib/cordova';
import { firstValueFrom } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NgHttpService implements Requestor {

  constructor(private http: HttpClient) {
  }

  public async xhr<T>(settings: XhrSettings): Promise<T> {
    if (!settings.method) {
      settings.method = 'GET';
    }

    switch (settings.method) {
      case 'GET':
        return firstValueFrom(this.http.get<T>(settings.url, { headers: this.getHeaders(settings.headers) }));
      case 'POST':
        return firstValueFrom(this.http.post<T>(settings.url, settings.data, { headers: this.getHeaders(settings.headers) }));
      case 'PUT':
        return firstValueFrom(this.http.put<T>(settings.url, settings.data, { headers: this.getHeaders(settings.headers) }));
      case 'DELETE':
        return firstValueFrom(this.http.delete<T>(settings.url, { headers: this.getHeaders(settings.headers) }));
    }
  }

  private getHeaders(headers: any): HttpHeaders {
    let httpHeaders: HttpHeaders = new HttpHeaders();

    if (headers !== undefined) {
      Object.keys(headers).forEach((key) => {
        httpHeaders = httpHeaders.append(key, headers[key]);
      });
    }

    return httpHeaders;
  }
}

You can also switch to Capacitor Http native implementation, and I personally switched to this implementation:

import { Injectable } from '@angular/core';
import { Requestor } from '@openid/appauth';
import { CapacitorHttp, HttpResponse  } from '@capacitor/core';
import { XhrSettings } from 'ionic-appauth/lib/cordova';

@Injectable({
  providedIn: 'root',
})
export class CapacitorHttpService implements Requestor {
  public async xhr<T>(settings: XhrSettings): Promise<T> {
    if (!settings.method) {
      settings.method = 'GET';
    }

    const response: HttpResponse = await CapacitorHttp.request({
      method: settings.method,
      url: settings.url,
      headers: settings.headers,
      data: settings.data,
    });
    return response.data as T;
  }
}

Please, try this fix and let me know if this was also your case.

Also, after this I encountered second issue. When request from loadUserInfo() method ends with 401 Http error code, boradcasted event is still LoadUserInfoSuccess instead of LoadUserInfoFailed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants