title: Stuck in redirect loop with identity server using the code authentication flow
body: <p>I've an Angular SPA app that I want to integrate with identity server 4.</p>
  
  <p>I'm trying to use the new recommended authentication flow using the angular-auth-oidc-client library.</p>
  
  <p>I've setup an Angular interceptor that executes at every request and tries to get a token, by forcing the login if the user is not authenticated. Its code is this:</p>
  
  <pre><code>import { HttpInterceptor, HttpRequest, HttpEvent, HttpHandler} from '@angular/common/http';
  import { Injectable } from '@angular/core';
  import { Observable } from 'rxjs';
  import { AuthService } from '../authentication/auth.service';
  
  @Injectable()
  export class TokenInterceptorService implements HttpInterceptor {
  constructor(private authService: AuthService) {}
    intercept(req: HttpRequest&lt;any&gt;, next: HttpHandler): Observable&lt;HttpEvent&lt;any&gt;&gt; {
        console.log('INTERCEPTOR' + this.authService);
        //// We retrieve the token, if any
        let token = this.authService.getToken();
        let newHeaders = req.headers;
        if (!token) {
          this.authService.login();
        }
  
        token = this.authService.getToken();
        newHeaders = newHeaders.append('bearer', token);
        //// Finally we have to clone our request with our new headers
        //// This is required because HttpRequests are immutable
        const authReq = req.clone({headers: newHeaders});
        //// Then we return an Observable that will run the request
        //// or pass it to the next interceptor if any
        return next.handle(authReq);
     }
  }
  </code></pre>
  
  <p>The AuthService implementation looks like this</p>
  
  <pre><code>import { Injectable, OnDestroy, Inject } from '@angular/core';
  import { OidcSecurityService, OpenIdConfiguration, AuthWellKnownEndpoints, AuthorizationResult, AuthorizationState } from 'angular-auth-oidc-client';
  import { Observable ,  Subscription, throwError } from 'rxjs';
  import { catchError } from 'rxjs/operators';
  import { HttpHeaders, HttpClient, HttpParams } from '@angular/common/http';
  import { Router } from '@angular/router';
  
  @Injectable()
  export class AuthService implements OnDestroy {
  
      isAuthorized = false;
      public bearerToken: string | null = null;
      public sessionState: number | null = null;
  
      constructor(
          private oidcSecurityService: OidcSecurityService,
          private http: HttpClient,
          private router: Router,
          @Inject('BASE_URL') private originUrl: string,
          @Inject('AUTH_URL') private authUrl: string,
      ) {
      }
  
      private isAuthorizedSubscription: Subscription = new Subscription;
  
      ngOnDestroy(): void {
          if (this.isAuthorizedSubscription) {
              this.isAuthorizedSubscription.unsubscribe();
          }
      }
  
      public initAuth() {
          const openIdConfiguration: OpenIdConfiguration = {
              stsServer: this.authUrl,
              redirect_url: this.originUrl + 'callback',
              client_id: 'myApp.api',
              response_type: 'code',
              scope: 'myApp.api',
              post_logout_redirect_uri: this.originUrl,
              forbidden_route: '/forbidden',
              unauthorized_route: '/unauthorized',
              silent_renew: true,
              silent_renew_url: this.originUrl,
              history_cleanup_off: true,
              auto_userinfo: true,
              log_console_warning_active: true,
              log_console_debug_active: true,
              max_id_token_iat_offset_allowed_in_seconds: 10,
          };
  
          const authWellKnownEndpoints: AuthWellKnownEndpoints = {
              issuer:'.office.sbs',
              jwks_uri: this.authUrl + '/.well-known/openid-configuration/jwks',
              authorization_endpoint: this.authUrl + '/connect/authorize',
              token_endpoint: this.authUrl + '/connect/token',
              userinfo_endpoint: this.authUrl + '/connect/userinfo',
              end_session_endpoint: this.authUrl + '/connect/endsession',
              check_session_iframe: this.authUrl + '/connect/checksession',
              revocation_endpoint: this.authUrl + '/connect/revocation',
              introspection_endpoint: this.authUrl + '/connect/introspect',
          };
  
          this.oidcSecurityService.setupModule(openIdConfiguration, authWellKnownEndpoints);
  
          if (this.oidcSecurityService.moduleSetup) {
              this.doCallbackLogicIfRequired();
          } else {
              this.oidcSecurityService.onModuleSetup.subscribe(() =&gt; {
                  this.doCallbackLogicIfRequired();
              });
          }
          this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe((isAuthorized =&gt; {
              this.isAuthorized = isAuthorized;
          }));
  
          this.oidcSecurityService.onAuthorizationResult.subscribe(
              (authorizationResult: AuthorizationResult) =&gt; {
                  this.onAuthorizationResultComplete(authorizationResult);
              });
      }
  
      private onAuthorizationResultComplete(authorizationResult: AuthorizationResult) {
  
          console.log('Auth result received AuthorizationState:'
              + authorizationResult.authorizationState
              + ' validationResult:' + authorizationResult.validationResult);
  
          if (authorizationResult.authorizationState === AuthorizationState.unauthorized) {
              if (window.parent) {
                  // sent from the child iframe, for example the silent renew
                  this.router.navigate(['/unauthorized']);
              } else {
                  window.location.href = '/unauthorized';
              }
          }
          else if (authorizationResult.authorizationState === AuthorizationState.authorized) {
              console.log("authenticated");
          }
      }
  
      private doCallbackLogicIfRequired() {
  
          //this.oidcSecurityService.authorizedCallbackWithCode(window.location.toString());
  
          console.log(window.location);
  
          const urlParts = window.location.toString().split('?');
          const params = new HttpParams({
              fromString: urlParts[1]
          });
          const code = params.get('code');
          const state = params.get('state');
  
  
          if (code &amp;&amp; state &amp;&amp; this.sessionState === null) {
              this.sessionState = Math.random() * 50000;
              this.oidcSecurityService.requestTokensWithCode(code, state, this.sessionState.toString());
          }
      }
  
      getIsAuthorized(): Observable&lt;boolean&gt; {
          return this.oidcSecurityService.getIsAuthorized();
      }
  
      login() {
          console.log('start login');
          this.oidcSecurityService.authorize();
      }
  
      logout() {
          console.log('start logoff');
          this.oidcSecurityService.logoff();
      }
  
      get(url: string): Observable&lt;any&gt; {
          return this.http.get(url, { headers: this.getHeaders() })
          .pipe(catchError((error) =&gt; {
              this.oidcSecurityService.handleError(error);
              return throwError(error);
          }));
      }
  
      put(url: string, data: any): Observable&lt;any&gt; {
          const body = JSON.stringify(data);
          return this.http.put(url, body, { headers: this.getHeaders() })
          .pipe(catchError((error) =&gt; {
              this.oidcSecurityService.handleError(error);
              return throwError(error);
          }));
      }
  
      delete(url: string): Observable&lt;any&gt; {
          return this.http.delete(url, { headers: this.getHeaders() })
          .pipe(catchError((error) =&gt; {
              this.oidcSecurityService.handleError(error);
              return throwError(error);
          }));
      }
  
      post(url: string, data: any): Observable&lt;any&gt; {
          const body = JSON.stringify(data);
          return this.http.post(url, body, { headers: this.getHeaders() })
          .pipe(catchError((error) =&gt; {
              this.oidcSecurityService.handleError(error);
              return throwError(error);
          }));
      }
  
      private getHeaders() {
          let headers = new HttpHeaders();
          headers = headers.set('Content-Type', 'application/json');
          return this.appendAuthHeader(headers);
      }
  
      public getToken() {
          const token = this.oidcSecurityService.getToken();
  
          return token;
      }
  
      private appendAuthHeader(headers: HttpHeaders) {
          const token = this.oidcSecurityService.getToken();
  
          if (token === '') { return headers; }
  
          const tokenValue = 'Bearer ' + token;
          return headers.set('Authorization', tokenValue);
      }
  }
  </code></pre>
  
  <p>The main issue is that when I hit the request asking for the token in angular-auth-oidc-client.js (code below), the requests keeps flipping back between the identity server and the callback page and the code keeps hitting the method doCallbackLogicIfRequired.</p>
  
  <pre><code>OidcSecurityService.prototype.requestTokensWithCodeProcedure$ = 
      // Code Flow with PCKE
      /**
       * @param {?} code
       * @param {?} state
       * @param {?} session_state
       * @return {?}
       */
      function (code, state, session_state) {
          var _this = this;
          /** @type {?} */
          var tokenRequestUrl = '';
          if (this.configurationProvider.wellKnownEndpoints &amp;&amp; this.configurationProvider.wellKnownEndpoints.token_endpoint) {
              tokenRequestUrl = "" + this.configurationProvider.wellKnownEndpoints.token_endpoint;
          }
          if (!this.oidcSecurityValidation.validateStateFromHashCallback(state, this.oidcSecurityCommon.authStateControl)) {
              this.loggerService.logWarning('authorizedCallback incorrect state');
              // ValidationResult.StatesDoNotMatch;
              return throwError(new Error('incorrect state'));
          }
          /** @type {?} */
          var headers = new HttpHeaders();
          headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
          /** @type {?} */
          var data = oneLineTrim(templateObject_1 || (templateObject_1 = __makeTemplateObject(["grant_type=authorization_code&amp;client_id=", "\n            &amp;code_verifier=", "\n            &amp;code=", "&amp;redirect_uri=", ""], ["grant_type=authorization_code&amp;client_id=", "\n            &amp;code_verifier=", "\n            &amp;code=", "&amp;redirect_uri=", ""])), this.configurationProvider.openIDConfiguration.client_id, this.oidcSecurityCommon.code_verifier, code, this.configurationProvider.openIDConfiguration.redirect_url);
          if (this.oidcSecurityCommon.silentRenewRunning === 'running') {
              data = oneLineTrim(templateObject_2 || (templateObject_2 = __makeTemplateObject(["grant_type=authorization_code&amp;client_id=", "\n                &amp;code_verifier=", "\n                &amp;code=", "\n                &amp;redirect_uri=", ""], ["grant_type=authorization_code&amp;client_id=", "\n                &amp;code_verifier=", "\n                &amp;code=", "\n                &amp;redirect_uri=", ""])), this.configurationProvider.openIDConfiguration.client_id, this.oidcSecurityCommon.code_verifier, code, this.configurationProvider.openIDConfiguration.silent_renew_url);
          }
          return this.httpClient.post(tokenRequestUrl, data, { headers: headers }).pipe(map((/**
           * @param {?} response
           * @return {?}
           */
          function (response) {
              /** @type {?} */
              var obj = new Object();
              obj = response;
              obj.state = state;
              obj.session_state = session_state;
              _this.authorizedCodeFlowCallbackProcedure(obj);
              return undefined;
          })), catchError((/**
           * @param {?} error
           * @return {?}
           */
          function (error) {
              _this.loggerService.logError(error);
              _this.loggerService.logError("OidcService code request " + _this.configurationProvider.openIDConfiguration.stsServer);
              return throwError(error);
          })));
      };
  </code></pre>
  
  <p>By checking the console log, I can see I got a token</p>
  
  <pre><code>VM1604 app.bundle.js:11364 INTERCEPTOR[object Object]
  VM1604 app.bundle.js:1527 start login
  VM1604 app.bundle.js:89392 BEGIN Authorize Code Flow, no auth data
  VM1604 app.bundle.js:89392 AuthorizedController created. local state: 15821982471040.47561711387714610.5390697281765799
  VM1604 app.bundle.js:75476 Angular is running in the development mode. Call enableProdMode() to enable the production mode.
  VM1604 app.bundle.js:89392 BEGIN authorized Code Flow Callback, no auth data
  VM1604 app.bundle.js:89392 history clean up inactive
  VM1604 app.bundle.js:89392 {access_token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IkJFODdGRjgwRjhBMkQyMT…V9-uKxgRq46JSiVpZbUSm_d7GB07aO1k4SvRWmIqw20Fo7ADA", expires_in: 3600, token_type: "Bearer", state: "15821982471040.47561711387714610.5390697281765799", session_state: "45871.95561094084"}
  VM1604 app.bundle.js:89392 authorizedCallback created, begin token validation
  VM1604 app.bundle.js:89392 jwks_uri: https://mySecurityMachine.myDomain/authentication/.well-known/openid-configuration/jwks
  VM1604 app.bundle.js:11364 INTERCEPTOR[object Object]
  VM1604 app.bundle.js:1527 start login
  VM1604 app.bundle.js:89392 BEGIN Authorize Code Flow, no auth data
  VM1604 app.bundle.js:89392 AuthorizedController created. local state: 15821982471040.47561711387714610.5390697281765799
  log.js:24 [HMR] Waiting for update signal from WDS...
  log.js:24 [HMR] Waiting for update signal from WDS...
  Navigated to https://localhost.myDomain:8083/callback?code=b30915bfb9a0f8ea838b685b467fd33c02c3e3282210e9ca11807665a8116d87&amp;scope=cc.api&amp;state=15821982471040.47561711387714610.5390697281765799
  log.js:24 [HMR] Waiting for update signal from WDS...
  angular-auth-oidc-client.js:392 STS server: https://mySecurityMachine.myDomain/authentication
  angular-auth-oidc-client.js:392 Silent Renew is active, check if token in storage is active
  angular-auth-oidc-client.js:392 &lt;iframe id=​"myiFrameForSilentRenew" style=​"display:​ none;​"&gt;​…​&lt;/iframe&gt;​
  auth.service.ts:105 Location {href: "https://localhost.myDomain:8083/callback?code=b3…15821982471040.47561711387714610.5390697281765799", ancestorOrigins: DOMStringList, origin: "https://localhost.myDomain:8083", protocol: "https:", host: "localhost.myDomain:8083", …}
  </code></pre>
  
  <p>As shown in </p>
  
  <pre><code>VM1604 app.bundle.js:89392 {access_token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IkJFODdGRjgwRjhBMkQyMT…V9-uKxgRq46JSiVpZbUSm_d7GB07aO1k4SvRWmIqw20Fo7ADA", expires_in: 3600, token_type: "Bearer", state: "15821982471040.47561711387714610.5390697281765799", session_state: "45871.95561094084"}
  </code></pre>
  
  <p>What am I missing to get redirected to the desired page?</p>