Reputation: 2344
I am using angular 8 to make a SPA.
Firebase is used to authenticate the user both in the client as well as in the backend, so I need to send the jwt token in http.get request to the backend to authenticate the user.
Backend is an API made with django 2.2 and django rest framework which sends the api to be consumed in client application.
auth.service.ts
@Injectable({
providedIn: 'root'
})
export class AuthService {
userData: any; // Save logged in user data
public userToken: string;
constructor(
public afs: AngularFirestore, // Inject Firestore service
public afAuth: AngularFireAuth, // Inject Firebase auth service
public router: Router,
public ngZone: NgZone // NgZone service to remove outside scope warning
) {
/* Saving user data in localstorage when
logged in and setting up null when logged out */
this.afAuth.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
GetToken(): string {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
// this shows the userToken
console.log('token inside getToken method ' + this.userToken);
});
}
});
// this shows userToken as undefined
console.log('before return ' + this.userToken);
return this.userToken;
}
}
api.service.ts
@Injectable({
providedIn: 'root'
})
export class ApiService {
private url = environment.baseUrl;
token: any;
data: any;
constructor(
private http: HttpClient,
private authService: AuthService,
) {}
// old method to get emloyees data
// public getEmployees(): Observable<Employee[]> {
// return this.http.get<Employee[]>(`${this.url}/employee/`);
// }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + this.authService.GetToken()
}),
};
public getEmployees(): Observable<Employee[]> {
// token is undefined here
console.log('token inside getEmployees method ' + this.token);
return this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions);
}
}
The backend is working perfectly which I verified by adding the token in the httpOptions, like so:
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + 'ey.....'
}),
};
But when I try doing the same as given in code it doesn't work. The user token remains undefined.
Upvotes: 3
Views: 4944
Reputation: 599571
Peter's answer has the crux of it: getIdToken()
is asynchronous, so by the time your return this.userToken;
runs, the this.userToken = idToken;
hasn't run yet. You should be able to see this from the output of your console.log
statements.
For more on this see How to return value from an asynchronous callback function? I highly recommend studying this answer for a while, as this asynchronous behavior is incredibly common when dealing with web APIs.
The fix for your code is to return a Promise
, instead of trying to return the value:
GetToken(): Promise<string> {
return new Promise((resolve, reject) => {
this.afAuth.auth.onAuthStateChanged( user => {
if (user) {
user.getIdToken().then(idToken => {
this.userToken = idToken;
resolve(idToken);
});
}
});
})
}
In words: GetToken
returns a promise that resolves once an ID token is available. If you know the user is already signed in when you call this function, you can simplify it to:
GetToken(): string {
const user = firebase.authentication().currentUser;
return user.getIdToken()
}
The difference is that the second function does not wait for the user to be signed in, so will fail if there is no signed in user.
You then use either of the above functions like this in getEmployees
:
public getEmployees(): Observable<Employee[]> {
return new Promise((resolve, reject) =>
this.authService.GetToken().then((idToken) => {
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'JWT ' + idToken
}),
};
this.http.get<Employee[]>(`${this.url}/employee/`, this.httpOptions)
.then(resolve).catch(reject);
})
})
}
Upvotes: 7
Reputation: 80924
It is undefined here console.log('before return ' + this.userToken);
because getIdToken()
returns a Promise
which means it is asynchronous, therefore the only way to access the userToken
is inside the then()
method.
Upvotes: 3