Why IdentityServer 4 code flow gets invalid_grant response some times later?

2.3k Views Asked by At

I am using identityserver4 code flow for my angular application. I am using angular-oauth2-oidc library.

My configuration is like this:

   OauthConfig: AuthConfig = {
        issuer: 'http://mydomain.identityserver4',
        requireHttps: false,
        responseType: "code",
        redirectUri: window.location.origin + '/index.html',
        clientId: 'dev.code.flow',
        scope: 'openid profile offline_access my.api',
        logoutUrl: window.location.origin + '/index.html',
        postLogoutRedirectUri: window.location.origin + '/index.html'
    }

  private configureOauth(){
    this.oauthService.configure(OauthConfig);
    this.oauthService.tokenValidationHandler = new JwksValidationHandler();
    this.oauthService.loadDiscoveryDocumentAndLogin();
    this.oauthService.setupAutomaticSilentRefresh();
  }

After I login the application, the library sends refresh token request every 5 minutes. I can see this in chrome developer tools.

But several hours later, the token refresh request gets a 400 (Bad request) error. Error message is error: "invalid_grant"

What could be the reason for this?

1

There are 1 best solutions below

3
On

As per the OAuth2.0 protocol, both Refresh Token and Access Token has some restricted lifetime.

Looks like your Refresh Token is getting expired after some hours. To tackle this issue you can

  • Increase the Refresh Token lifetime (generally they are long-lived and should be handled securely)
  • Ask the user to log in again.
  • Can use sliding expiration mode.

This is from the IdentityServer4 documentation (link):

AbsoluteRefreshTokenLifetime: Maximum lifetime of a refresh token in seconds. Defaults to 2592000 seconds / 30 days. Zero allows refresh tokens that, when used with RefreshTokenExpiration = Sliding only expire after the SlidingRefreshTokenLifetime is passed.

You can configure the Refresh Token Timeout in the IdentityServer4's client config.

Following is the method I used to redirect the user to the login page:

setupAutomaticLogoutInCaseOfTokenExpiry() {
    if (!this.oauthService.events) {
      return;
    }

    this.oauthService.events.subscribe((x: any) => {
      if (x.type === 'token_refresh_error') {
        // In case of internet connectivity
        if (x.reason && x.reason.error && x.reason.error.error === 'invalid_grant' &&
            x.reason.error.error_reason === 'refresh_token_not_found') {
          this.oauthService.logOut(true);
          (window as any).removeAllListeners();
          this.router.navigateByUrl(`/${Constants.PageUrls.Login}`);
        } else {
          // In case of no internet connectivity
          this.oauthService.stopAutomaticRefresh();
          const rereshTimeout = setTimeout(() => {
            this.oauthService.refreshToken().then(() => {
              this.oauthService.setupAutomaticSilentRefresh();
              clearTimeout(rereshTimeout);
            })
            .catch(() => {
              clearTimeout(rereshTimeout);
            });
          }, 5000);
        }
      }
    });
}