Can't pass bearer token in HTTP header, blank constant?

I have been playing with OAuth and connecting to an API I have built. I can test it and it works just fine in both Postman and when specifying my key as a string but I can’t get the bearer token to be sent with the HTTP GET request. I’m stumped.

My code is as follows:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Storage } from '@capacitor/storage';
import { AlertController } from '@ionic/angular';
import { tap } from 'rxjs/operators';
import { EnvService } from './env.service';
@Injectable({
  providedIn: 'root'
})
export class DeploymentService {

  token = '';

  constructor(
    private http: HttpClient,
    private env: EnvService,
    private alertCtrl: AlertController
  ) { }

  async test() {
    const { value } = await Storage.get({ key: 'token-key' });
    this.token = value;
    const alert = await this.alertCtrl.create({
      header: 'BEARER',
      message: this.token,
      buttons: ['OK']
    });
    alert.present();
  };

  getDeployments() {

    const headers = { 'Authorization': `Bearer ${this.token}` }

    return this.http
      .get(this.env.API_URL + '/api/deployments', { headers: headers })
      .pipe(tap(resData => {
        console.log(resData);
      }))
  }
}

I then call it from the appropriate page:

ionViewWillEnter() {
    this.deploymentService.getDeployments().subscribe();
    this.deploymentService.test();
}

As you can see, I have included an alert controller, more so I can test the token constant was actually being modified. But whenever I try to use the same logic to pass token in to the header, I get the 401 because the constant is blank.

It does work if I manually include the token as the value to the constant but doesn’t work any other way.

Where am I going wrong?

A functional programmer would say you’re going wrong by modifying external state from inside a function. Another way of looking at it would say that you’re going wrong by falling for the fiction that async and await are magical time machines that make asynchronous things happen synchronously.

Personally, I don’t use or recommend async and await for precisely this reason. They obscure what actually is happening and present a facade that tempts us to write things like you have here.

The bottom line is that nobody outside test has any clue when the token has been received from storage. Yet, you rely on it having been set from inside getDeployments. Result: race condition.

So here’s what I would recommend: get rid of async and await, and instead provide proper return types for every function you write. Follow the advice in taxonomy of asynchronous beasts and make the first word of every class C asynchronous function you write be return.

This situation is exactly why I give both sides of that advice: it forces us to think what test should return (some sort of future), and locks us into returning the very beginning of the chain of events, which is going to start with the Promise returned from Storage.get.

So you would end up with something structured like this (renaming test to something more descriptive of what it’s actually doing):

fetchToken(): Promise<string> {
  return Storage.get({key: "token-key"});
}

getDeployments(): Observable<Deployment[]> {
  return from(this.fetchToken()).pipe(
    switchMap(token => this.http.get(`${this.env.API_URL}/api/deployments`, { headers: { 'Authorization': `Bearer ${token}`));
}

In the real world, you don’t likely want to be hitting Storage every time here, so you would make your token property a Promise or a Subject, feed it at app startup, and reference it from inside getDeployments. Don’t make it a naked value, though, like you have here, because it’s then impossible for consumers to know when they can reliably access it, as you have discovered the hard way.

Thanks @rapropos, I appreciate you taking the time.

I’ve tried to implement the code you have suggested but unfortunately it still doesn’t work.

I think I need to do some more reading on Angular Observables and Promises.

Fixed it! Finally.

I did a bit more reading with the direction from @rapropos answer.

Essentially I’d forgotten to add a call back to get the token value which I then use to add to the GET request. My finished fetchToken() function looks like this:

fetchToken(): Promise<string> {
    return Storage.get({ key: 'token-key' }).then(token => {
      return token.value;
    });
  }

Please don’t tell me I’ve done something catastrophically wrong… I’ve spent two days trying to master this!