Authentication in Angular: The Circular Dependency in DI issue popping its ugly head
Tags: angular, webdev, auth, tutorial
Published: 2023-02-07 12:00:00

Picking up where we left off, the first use case for our Auth service is a header token. The best way to add a header token is via an Http interceptor. Let's start.

Since we are injecting the `AuthService` anyway, and using `AppModule`, it does not make much difference to use `HttpInterceptorFunction` instead of the goold ol' `HttpClientModule`. Down the line it will be more evident that it is indeed a better choice.

You can read about [Angular 15 standalone HTTPClient provider](https://garage.sekrab.com/posts/angular-standalone-httpclient-provider).

Follow along on [StackBlitz](https://stackblitz.com/edit/auth-angular)

In our App Module provider array, we add another entry for the interceptor:

```ts
// app.module
@NgModule({
	// ...	
	providers: [
		{
	    provide: HTTP_INTERCEPTORS,
	    multi: true,
	    useClass: AppInterceptor,
	  },
		// ...
	]
})
export class AppModule {}
```

The interceptor injects the `AuthService` immediately to use it. Let me add a console log as the first line.

```ts
// services/http HttpInterceptor
@Injectable()
export class AppInterceptor implements HttpInterceptor {
  constructor(private authState: AuthState) {
    console.log('interceptor injected');
  }
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // prefixing the api with proper value, mostly from config
    // remote config url are expected to be filtered out, it would not make sense
    const url = 'https://saphire.sekrab.com/api' + req.url;

    const adjustedReq = req.clone({
      url: url,
      setHeaders: this.getHeaders(),
    });

    return next.handle(adjustedReq);
  }
  private getHeaders(): any {
    // TODO authorization here
    let headers: any = {};

    return headers;
  }
}
```

Before we add our header, let's remember our sequence of events: assuming we are using Http call to get remote configuration, which normally has the correct API URL, it is clear that we need to filter out the configuration URL. In this example, I am not calling a remote URL for configurations, but it's good to know that the interceptor should check the `req.url` and filter out the ones it does not wish to deal with.

```ts
// simple check to exclude local data or config url
if (req.url.indexOf('config') > -1) {
	// pass through
	return next(req);
}
```

### The Circular Dependency in DI issue

`Error: NG0200: Circular dependency in DI detected for InjectionToken HTTP_INTERCEPTORS.`

Have you ever seen this? It occurs when you inject a service in another service, that injects it back in itself. In our case, the `AuthService` and `HttpClient` kind of inject each other.

In addition to those two services, the **configuration service** that uses Http is also injected in `AuthService`. It's a mess no matter how you look at it.

But here is the thing that will kill you before you reach midlife. Since we are not making any use of the `HttpClient` in the `AuthService` **constructor**, this tumor is benign. If however we do initiate an Http call in the constructor, that's when it blows up in our faces.

There are many fixes, most of them are around delaying the Http call a bit just to make sure the `AuthService` has been constructed. Like waiting for the remote configuration to be ready. But that isn't a clean cut solution.

> That settles it then, as a general rule: avoid Http requests in your service constructors. Especially those injected early on.

If indeed you need to inject a service that calls an Http in its constructor (next week we'll have a use case for that), break your services apart, and spread them around in your app.

[](https://garage.sekrab.com/posts/authentication-in-angular-the-circular-dependency-in-di-issue-popping-its-ugly-head?draft=true&debug=true#AuthState-service)AuthState service
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

To clean up and be more systematic as we move forward let's move all non Http related methods to their own service. The `AuthState` is the service that will hold the `Observable` state, and contain no references to `HttpClient`. The constructor is responsible for reading the `LocalStorage` information, and it has the `GetToken` new method to return the token.

```ts
// services/auth.state

@Injectable({ providedIn: 'root' })
export class AuthState {
  // create an internal subject and an observable to keep track
  private stateItem: BehaviorSubject<IAuthInfo | null> = new BehaviorSubject(
    null
  );
  stateItem$: Observable<IAuthInfo | null> = this.stateItem.asObservable();

  constructor() {
    // simpler to initiate state here
    // check item validity
    console.log('authState in');
    const _localuser: IAuthInfo = JSON.parse(localStorage.getItem('user'));
    if (this.CheckAuth(_localuser)) {
      this.SetState(_localuser);
    } else {
      this.Logout();
    }
  }
// also move here: SetState  RemoveState CheckAuth  Logout
}
```

Now the `AuthService` is much simpler, it only has the Login, and it uses `AuthState` to save into `localStorage`. We will enhance this later when we use a proper `localStorage` wrapper.

So now we need to create a `GetToken` method, to retrieve the access token, then use it in `HttpInterceptor`

```ts
// services/auth.state
// add this new method
GetToken() {
  const _auth = this.stateItem.getValue();
  // check if auth is still valid first before you return
  return this.CheckAuth(_auth) ? _auth.accessToken : null;;
}
```

We will add the logic for checking the token later. Then let's use it in the interceptor

```ts
// update http file to fill out get headers
private getHeaders(): any {
  //  authorization here
  let headers: any = {};
	const _auth = this.authState.GetToken();
	if (_auth && _auth !== '') {
	  headers['authorization'] = `Bearer ${_auth}`;
	}
	return headers;
}
```

In Module-full solution, everything is a private member of our Class. In standalone, everything is a loose variable. Which way do you prefer?

[](https://garage.sekrab.com/posts/authentication-in-angular-the-circular-dependency-in-di-issue-popping-its-ugly-head?draft=true&debug=true#401-Refresh)401 Refresh
--------------------------------------------------------------------------------------------------------------------------------------------------------------------

What happens when we get a 401? We can either show the user out, or use our refresh token to get a new access token. In Angular, this probably is one of the tasks that left me bruised for a while. Here is the sequence of events:

-   Catch a 401 (which 401)
-   Create a new Http call with refresh token, and request a new access token
-   Wait for response
-   Update localStorage
-   Resubmit original request (retry)
-   Return and have a nice life
-   Else logout
-   Catch another concurrent 401, queue and wait

So let's first modify the Http function to catch 401, and call a function for it.

```ts
// services/http

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // ...

    return next.handle(adjustedReq).pipe(
      catchError(error => {
         // if this is really an http error
         if (error instanceof HttpErrorResponse
            // and of 401 status
            && error.status === 401
          ){
					// handle 401 error, return an observable to continue the pipe
          return this.handle401Error();
         }
         // rethrow error, to be caught elsewhere
         return throwError(() => error);
      })
    );
  }

  private handle401Error(): Observable<any> {
    // let's first try to submit a refresh access token request
    // return authService.RefreshToken()
    // switchMap when done to resubmit the req passed, using next.handler
    // catchError means it is not working, rethrow and logout
  }
```

What we want to do now, is fill out the `handle401Error` function. First, it looks like we need a `RefreshToken` method in `AuthService` (not `AuthState`). Which means we need to inject that as well. Remember: `AuthService` has no Http calls in the constructor.

```ts
// services/auth.service
// add RefreshToken method
RefreshToken(): Observable<boolean> {
  return this.http
    .post(this._refreshUrl, { token: this.authState.GetRefreshToken() })
    .pipe(
      map((response) => {
        // this response has the new refresh token and access token
       if (!response) {
          // something terrible happened
          throw(new Error('Oh oh'));
        }

        // update session
        const retUser: IAuthInfo = <IAuthInfo>(<any>response).data;
        // we'll be more selective later...
        localStorage.setItem('user', JSON.stringify(retUser));

        this.authState.SetState(retUser);

        return true;
      })
    );
}
```

Back to our `handle401Error` function

```ts
// services/http
// update handle401Error function, also, inject AuthService in the constructor
private handle401Error(
	// pass in orginalReq and handler
	originalReq: HttpRequest<any>,
  next: HttpHandler
): Observable<any> {
	return this.authService.RefreshToken().pipe(
	  switchMap((result: boolean) => {
	    if (result) {
	      // token saved (in RefreshToken), now recall the original req after adjustment
		    // so we need to pass "next" handler, and "originalReq"
	      return next.handle(originalReq.clone({setHeaders: this.getHeaders()}));
	    }
	  }),
	  catchError(error => {
	    // else refresh token did not work, its bigger than both of us
	    // log out and throw error
	    this.authState.Logout();
	    return throwError(() => error);
	  })
	);
}
```

We adjust the signature to pass in the `originalReq` and the `next` handler:

```ts
// services/http
// adjust call
return next.handle(adjustedReq).pipe(
  catchError((error) => {
	    // ...
      return this.handle401Error(adjustedReq, next);
    }
		// ...
  })
);
```

Testing this, the first issue is the `/login` point. If it 401's, there is no need to retry, it just means bad credentials. So the handler must filter out `/login` point

```ts
// services/http filter out login from handler401Error
return next.handle(adjustedReq).pipe(
  catchError((error) => {
    // if this is really an http error
    if (
      error instanceof HttpErrorResponse &&
      // and of 401 status
      error.status === 401 &&
      // filter out login calls
      req.url.indexOf('login') < 0
    ) {
      return this.handle401Error(adjustedReq, next);
    }
    // rethrow error
    return throwError(() => error);
  })
```

Testing this by making a call in some page, and hard coding few things on my test server, this is what I get of logging out the sequence:

![Console log]
(https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebtatd3rlwaev6veumc0.png)

So you can see that the original request was recalled, with the proper access token. (Don't mind the value 'access_token_1', it's dummy date.)

> You want to produce similar colorful log? Read [Taming the console](https://garage.sekrab.com/posts/writing-a-wrapper-for-console-log-for-better-control-in-javascript-part-i)

### Locking and unlocking

We're not done yet. Let's create an example usage to see the problem that comes out of this. We are going to make two requests in parallel. This means that while the first request is trying to refresh token, the second request comes in, and it might request a new token as well, screwing up the original token. Here is the dummy log, that does not break the system, because well, its dumb:

![Console log out](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hwvt0ukvgqnbe4he03nm.png)

Notice the following:

-   Two 401 errors were thrown, that's expected
-   Two calls to refresh token, with the same refresh token, one should work, the other should not
-   Response comes in with new access token, in my example it's the same, because it's dumb. In real life there will be two different access tokens, one must fail (if it hadn't already)

To fix that we need to lock, queue, then unlock.

It's straightforward to lock and unlock, using a private Boolean member:

```ts
// services/http
// add lock boolean
@Injectable()
export class AppInterceptor implements HttpInterceptor {
  // if refreshing token, it is busy, lock
  isBusy: boolean;
  private handle401Error(
    originalReq: HttpRequest<any>,
    next: HttpHandler
  ): Observable<any> {

    if (!this.isBusy) {
			// lock
      this.isBusy = true;

      return this.authService.RefreshToken().pipe(
      // ...
				finalize(() => {
					// unlock
          this.isBusy = false;
       })
      );
    } else {
      // return unadjusted, for now
      return next.handle(originalReq);
    }
  }
}
```

Now with this, one call would retry, and all other would fail. We need to just wait a bit until the token is ready, before we adjust and recall all other ones. To do that, we can have a private member to keep track of the successful token. Once it's ready, flush out.

![Console log](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhz1q2gslyh954c0qz61.png)

The most widely accepted solution to this is using a `Subject` of `Boolean` and piping on it. It updates when locking, and when token is ready.

```ts
// services/http update to allow subject queuing
@Injectable()
export class AppInterceptor implements HttpInterceptor {

	// create a subject to queue outstanding refresh calls
  recall: Subject<boolean> = new Subject();
 // ...
 private handle401Error(...): Observable<any> {
   if (!this.isBusy) {
    // ...
    // progress subject to false
    this.recall.next(false);
    return this.authService.RefreshToken().pipe(
      switchMap((result: boolean) => {
        if (result) {
          // progress subject to true
          this.recall.next(true);
    			// ... return next.handle
        }
      }),
      // ...
    );
  } else {
    // return the subject, watch when it's ready, switch to recall original request
    return this.recall.pipe(
      filter(ready => ready === true),
      switchMap(ready => {
         // try again with adjusted header
         return next.handle(originalReq.clone({ setHeaders: this.getHeaders() }));
      })
     );
    }
}
```

I tried to break it, but I couldn't. If you run into scenarios where it is acting up, let me know please.

### Side point

You might be tempted to stop an outgoing request if the access token is not valid (expired). Don't. That is an API decision. Some points do not need an access token (like `/login`), some are flexible in returning less data if token is not valid.

### Enhance

One enhancement we can add is to redirect user to login page if the refresh token fails.

Another enhancement is in the login resolve. We can now save the URL that caused the redirection in the auth state, and try to redirect to it after login. That and one more rant about user account details is coming up next week. 😴

Thank you for reading this far, did you break the 401 handler?

### RESOURCES

-   [StackBlitz project](https://stackblitz.com/edit/auth-angular)