Search code examples
angularoauth-2.0google-oauthangular-oauth2-oidc

Error in silent refresh of token with google account


In an angular CLI web application I need to authenticate users with google accounts.

I'm using the angular_oauth_oidc library to manage all oauth-related tasks. I've configured the OAutService for automatic silent refresh. I can see in fiddler that the service is making the request for token refresh, but the response is an error.

This is the request (without sensitive information) to accounts.google.com:

GET /o/oauth2/v2/auth?response_type=id_token%20token&client_id=[MY_CLIENT_ID]&state=[MY_STATE]&redirect_uri=http%3A%2F%2Flocalhost%3A4200%2Fassets%2Fsilent-refresh.html&scope=openid%20profile%20email&nonce=[MY_NONCE]&prompt=none&id_token_hint=[MY TOKEN] HTTP/1.1

And this is the response:

HTTP/1.1 302 Found
Content-Type: application/binary
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: Mon, 01 Jan 1990 00:00:00 GMT
Date: Thu, 17 Jan 2019 15:49:21 GMT
Location: http://localhost:4200/assets/silent-refresh.html#state=[STATE]&error_subtype=access_denied&error=interaction_required

According to the library documentation and the oidc standard, the request has all the required parameters but I can't make it work.

Did someone succeed making automatic silent token refresh with google accounts?

Thank you


Solution

  • You have this error:

    • error_subtype=access_denied
    • error=interaction_required

    If you include this in your client side application you should also see that on the console:

    import { OAuthErrorEvent } from 'angular-oauth2-oidc';
    // ...
    // Wherever you set up the library, do this:
    this.authService.events.subscribe(e => (e instanceof OAuthErrorEvent) ? console.error(e) : console.warn(e));
    

    Basically, the errors mean that your IDS refuses to send a token to the Angular application until the user has had some kind of interaction with the IDS. Typically this means either (re-)entering your username and/or password, or giving some kind of consent.

    On the client side, when this happens, you should call initImplicitFlow(). In my sample repository I do that along these lines:

    return this.oauthService.loadDiscoveryDocument()
      .then(() => this.oauthService.tryLogin())
      .then(() => {
        if (this.oauthService.hasValidAccessToken()) {
          return Promise.resolve();
        }
    
        // 2. SILENT LOGIN:
        return this.oauthService.silentRefresh()
          .then(() => Promise.resolve())
          .catch(result => {
            // Subset of situations from https://openid.net/specs/openid-connect-core-1_0.html#AuthError
            // Only the ones where it's reasonably sure that sending the
            // user to the IdServer will help.
            const errorResponsesRequiringUserInteraction = [
              'interaction_required',
              'login_required',
              'account_selection_required',
              'consent_required',
            ];
    
            if (result
              && result.reason
              && errorResponsesRequiringUserInteraction.indexOf(result.reason.error) >= 0) {
    
              // Enable this to ALWAYS force a user to login.
              this.oauthService.initImplicitFlow();
    
              // Should be superfluous, user should be redirected to IDS already...
              return Promise.reject();
            }
    
            return Promise.reject(result);
          });
      });