-
Notifications
You must be signed in to change notification settings - Fork 699
Description
Describe the bug
When OAuthModule is set up, on initial app load, the code flow behaves as expected. Redirection occurs as expected, tokens are stored in sessionStorage (as per default) and refresh token usage works as expected as well (at 75% life of token, refresh token is retrieved from storage and sent to IdP to get new tokens).
When the user now hits F5, the flow initiates, but the "token_expires" event fires twice at the same time with info "id_token". This retrieves new tokens twice, also meaning that 2 refresh tokens get generated. Only one of these is valid thereafter, though.
Once the token expire again the "token_expires" event only fires once. However, at that point it's a gamble whether or not the valid refresh token was saved to storage. If the valid token was saved everything works as expected. If not the IdP rejects the call saying the refresh token was invalid.
This ALSO happens with implicit flow. After F5'ing the token_expires event fires twice. But since implicit flow just logs the user back in there is no problem with invalid data. It is, however, a wasted call.
To Reproduce
Set up a standard Identity Server configuration with localhost:5000:
new Client
{
ClientId = "angular",
ClientName = "Angular Client",
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RequireClientSecret = false,
Description = "Code Client for the angular",
RequireConsent = false,
RedirectUris = new List<string> { "http://localhost:4200" },
PostLogoutRedirectUris = new List<string> { "http://localhost:4200" },
AllowedCorsOrigins = new List<string> { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId
},
// necesarry for refresh token
AllowOfflineAccess = true,
}
Setup a very simple Angular app with ng new:
app.module
...
imports: [
BrowserModule,
HttpClientModule,
OAuthModule.forRoot()
]
...
Switch the implementation of app.component to:
import { Component, OnInit } from '@angular/core';
import { AuthConfig, OAuthService, JwksValidationHandler } from '../lib/public_api';
import { from, of } from 'rxjs';
import { switchMap, mapTo, catchError } from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'oauth-test';
ngOnInit(): void {
this.oauthService.events.subscribe(x => console.log(x));
const authConfig = new AuthConfig();
authConfig.requireHttps = false;
authConfig.issuer = 'http://localhost:5000';
authConfig.redirectUri = 'http://localhost:4200';
authConfig.clientId = 'angular';
authConfig.responseType = 'code';
authConfig.scope = 'openid offline_access';
// timeout set to really low value to spend less time waiting for the error to occur
authConfig.timeoutFactor = 0.05;
this.oauthService.configure(authConfig);
this.oauthService.tokenValidationHandler = new JwksValidationHandler();
this.oauthService.setupAutomaticSilentRefresh();
from(this.oauthService.loadDiscoveryDocumentAndLogin()).subscribe();
}
constructor(private oauthService: OAuthService) {
}
}
Expected behavior
The "token_expires" event only ever fires once for the actual situation of a token expiring.
Desktop (please complete the following information):
- OS: Windows
- Browser Chrome (happens on all browsers)
- Version 76, happens on all versions
EDIT 1:
This problem also happens with the sample application provided with the repository.
Simply set the timeoutFactor on the config to some low value (0.01) and you can see the event being emitted twice. Also of note is that for an actual error to occur the identity server needs to be set to RefreshTokenUsage = OneTime (which generates a new refresh token every time). I believe the sample apps setting is set to ReUse, which circumvents the problem.