It kind of seems like I am going in circles here, and perhaps it's because of the use of so many subscriptions and having to chain them together.
I want to be able to refresh a token if it's expired using the refresh token. Here is what I have, and I would really appreciate a simple working example if possible.
In summary, how can I ensure that the AudienceService first checks if the token is valid, if not, it tries to refresh it using the refresh token, and then makes a call to the endpoint with the appropriate token?
app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router';
import { Http, RequestOptions } from '@angular/http';
import { ConfirmDialogModule, ListboxModule, PickListModule } from 'primeng/primeng';
import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header/header.component';
import { HomeComponent } from './components/home/home.component';
import { ListAudiencesComponent } from './components/audiences/list-audiences/list-audiences.component';
import { AudienceService } from './services/audience.service';
import { LoggingService } from './services/logging.service';
import { RoleService } from './services/role.service';
import { AuthService } from './services/auth.service';
import { UserService } from './services/user.service';
import { AuthGuard } from './services/auth-guard.service'
import { AuthHttp, AuthConfig, provideAuth } from 'angular2-jwt';
import { ListRolesComponent } from './components/roles/list-roles/list-roles.component';
import { EditRoleAudiencesComponent } from './components/roles/edit-role-audiences/edit-role-audiences.component';
import { ModifyRoleComponent } from './components/roles/modify-role/modify-role.component';
import { LoginComponent } from './components/login/login.component';
import { UnauthorizedComponent } from './components/unauthorized/unauthorized.component';
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(new AuthConfig({
tokenName: 'token',
tokenGetter: (() => sessionStorage.getItem('id_token')),
globalHeaders: [{'Content-Type':'application/json'}],
}), http, options);
}
@NgModule({
declarations: [
AppComponent,
HeaderComponent,
HomeComponent,
ListAudiencesComponent,
ListRolesComponent,
EditRoleAudiencesComponent,
ModifyRoleComponent,
LoginComponent,
UnauthorizedComponent
],
imports: [
BrowserModule,
ConfirmDialogModule,
FormsModule,
HttpModule,
ListboxModule,
PickListModule,
ReactiveFormsModule,
RouterModule.forRoot([
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'unauthorized', component: UnauthorizedComponent },
{ path: 'audiences', component: ListAudiencesComponent, canActivate: [AuthGuard] },
{ path: 'roles', component: ListRolesComponent, canActivate: [AuthGuard] },
{ path: 'roles/modify/:name', component: ModifyRoleComponent, canActivate: [AuthGuard] },
{ path: '**', redirectTo: 'home' }
]),
],
providers: [
{
provide: AuthHttp,
useFactory: authHttpServiceFactory,
deps: [Http, RequestOptions]
},
AudienceService, AuthGuard, AuthService, LoggingService, RoleService, UserService
],
bootstrap: [AppComponent]
})
export class AppModule { }
auth.service.ts:
import { Injectable } from '@angular/core';
import { Http, Headers, RequestOptions, URLSearchParams } from '@angular/http';
import { environment } from '../../environments/environment';
import { tokenNotExpired } from 'angular2-jwt';
@Injectable()
export class AuthService {
tokenEndpoint = environment.token_endpoint;
constructor(private http: Http ) { }
login(username: string, password: string) {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
let body = new URLSearchParams();
body.set('username', username);
body.set('password', password);
body.set('client_id', '099153c2625149bc8ecb3e85e03f0022');
body.set('grant_type', 'password');
console.log("Got here");
return this.http.post(this.tokenEndpoint, body, options)
.map(res => res.json())
.subscribe(
data => {
localStorage.setItem('id_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
},
error => console.log(error)
);
}
loggedIn() {
if (tokenNotExpired()) {
return true;
} else {
this.refreshToken()
.subscribe(
data => {
if (data.error) {
this.logout();
} else {
localStorage.setItem('id_token', data.access_token);
localStorage.setItem('refresh_token', data.refresh_token);
console.log("Token was refreshed.");
}
},
error => this.logout(),
() => {
return tokenNotExpired();
}
);
}
}
refreshToken() {
let refToken = localStorage.getItem('refresh_token');
if (refToken) {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
let body = new URLSearchParams();
body.set('client_id', '099153c2625149bc8ecb3e85e03f0022');
body.set('grant_type', 'refresh_token');
body.set('refresh_token', refToken);
return this.http.post(this.tokenEndpoint, body, options)
.map(res => res.json());
} else {
this.logout();
}
}
tokenRequiresRefresh(): boolean {
if (!this.loggedIn()) {
console.log("Token refresh is required");
}
return !this.loggedIn();
}
logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('refresh_token');
}
}
audience.service.ts:
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import { environment } from '../../environments/environment';
import { AuthHttp } from 'angular2-jwt';
import { AuthService } from './auth.service';
import { AddDeleteAudienceModel } from './AddAudienceModel';
@Injectable()
export class AudienceService {
baseApiUrl = environment.api_endpoint;
constructor(private http: Http, private authHttp: AuthHttp, private authService: AuthService) { }
getAllAudiences()
{
if (this.authService.tokenRequiresRefresh()) {
this.authService.refreshToken();
}
if (this.authService.loggedIn()) {
return this.authHttp.get(this.baseApiUrl + 'audience/all').map(res => res.json());
}
}
}
auth.service.ts
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Http, Headers, RequestOptions, URLSearchParams } from '@angular/http';
import { environment } from '../../environments/environment';
import { tokenNotExpired, JwtHelper } from 'angular2-jwt';
import { Subject, Observable } from 'rxjs';
@Injectable()
export class AuthService {
tokenEndpoint = environment.token_endpoint;
requireLoginSubject: Subject<boolean>;
tokenIsBeingRefreshed: Subject<boolean>;
lastUrl: string;
jwtHelper: JwtHelper = new JwtHelper();
constructor(private http: Http, private router: Router) {
this.requireLoginSubject = new Subject<boolean>();
this.tokenIsBeingRefreshed = new Subject<boolean>();
this.tokenIsBeingRefreshed.next(false);
this.lastUrl = "/home";
}
isUserAuthenticated() {
if(this.loggedIn()) {
this.requireLoginSubject.next(false);
return true;
} else {
return false;
}
}
login(username: string, password: string) {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
let body = new URLSearchParams();
body.set('username', username);
body.set('password', password);
body.set('client_id', '099153c2625149bc8ecb3e85e03f0022');
body.set('grant_type', 'password');
return this.http.post(this.tokenEndpoint, body, options).map(res => res.json());
}
loggedIn() {
return tokenNotExpired();
}
addTokens(accessToken: string, refreshToken: string) {
localStorage.setItem('id_token', accessToken);
localStorage.setItem('refresh_token', refreshToken);
}
getRefreshTokenExpirationDate() {
var token = localStorage.getItem('id_token');
if (token) {
let tokenExpDate = this.jwtHelper.getTokenExpirationDate(token);
let sessionExpDate = new Date(tokenExpDate.getTime() + 4*60000);
if (new Date() > sessionExpDate) {
this.logout();
}
return sessionExpDate;
}
return null;
}
hasRefreshToken() {
let refToken = localStorage.getItem('refresh_token');
if (refToken == null) {
this.logout();
}
return refToken != null;
}
refreshTokenSuccessHandler(data) {
if (data.error) {
console.log("Removing tokens.");
this.logout();
this.requireLoginSubject.next(true);
this.tokenIsBeingRefreshed.next(false);
this.router.navigateByUrl('/unauthorized');
return false;
} else {
this.addTokens(data.access_token, data.refresh_token);
this.requireLoginSubject.next(false);
this.tokenIsBeingRefreshed.next(false);
console.log("Refreshed user token");
}
}
refreshTokenErrorHandler(error) {
this.requireLoginSubject.next(true);
this.logout();
this.tokenIsBeingRefreshed.next(false);
this.router.navigate(['/sessiontimeout']);
console.log(error);
}
refreshToken() {
let refToken = localStorage.getItem('refresh_token');
//let refTokenId = this.jwtHelper.decodeToken(refToken).refreshTokenId;
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
let options = new RequestOptions({ headers: headers });
let body = new URLSearchParams();
body.set('client_id', '099153c2625149bc8ecb3e85e03f0022');
body.set('grant_type', 'refresh_token');
body.set('refresh_token', refToken);
return this.http.post(this.tokenEndpoint, body, options)
.map(res => res.json());
}
tokenRequiresRefresh(): boolean {
if (!this.loggedIn()) {
console.log("Token refresh is required");
}
return !this.loggedIn();
}
logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('refresh_token');
this.requireLoginSubject.next(true);
}
}
auth-http.service.ts
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import { environment } from '../../environments/environment';
import { AuthHttp } from 'angular2-jwt';
import { AuthService } from './auth.service';
@Injectable()
export class AuthHttpService {
constructor(private authHttp: AuthHttp, private authService: AuthService, private router: Router) { }
get(endpoint: string) {
if (this.authService.tokenRequiresRefresh()) {
this.authService.tokenIsBeingRefreshed.next(true);
return this.authService.refreshToken().switchMap(
(data) => {
this.authService.refreshTokenSuccessHandler(data);
if (this.authService.loggedIn()) {
this.authService.tokenIsBeingRefreshed.next(false);
return this.getInternal(endpoint);
} else {
this.authService.tokenIsBeingRefreshed.next(false);
this.router.navigate(['/sessiontimeout']);
return Observable.throw(data);
}
}
).catch((e) => {
this.authService.refreshTokenErrorHandler(e);
return Observable.throw(e);
});
}
else {
return this.getInternal(endpoint);
}
}
post(endpoint: string, body: string) : Observable<any> {
if (this.authService.tokenRequiresRefresh()) {
this.authService.tokenIsBeingRefreshed.next(true);
return this.authService.refreshToken().switchMap(
(data) => {
this.authService.refreshTokenSuccessHandler(data);
if (this.authService.loggedIn()) {
this.authService.tokenIsBeingRefreshed.next(false);
return this.postInternal(endpoint, body);
} else {
this.authService.tokenIsBeingRefreshed.next(false);
this.router.navigate(['/sessiontimeout']);
return Observable.throw(data);
}
}
).catch((e) => {
this.authService.refreshTokenErrorHandler(e);
return Observable.throw(e);
});
}
else {
return this.postInternal(endpoint, body);
}
}
private getInternal(endpoint: string) {
return this.authHttp.get(endpoint);
}
private postInternal(endpoint: string, body: string) {
return this.authHttp.post(endpoint, body);
}
}
audience.service.ts
import { Injectable } from '@angular/core';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
import { environment } from '../../environments/environment';
import { AuthHttpService } from './auth-http.service';
import { AddDeleteAudienceModel } from './AddAudienceModel';
@Injectable()
export class AudienceService {
baseApiUrl = environment.api_endpoint;
constructor(private authHttpService: AuthHttpService) { }
getAllAudiences()
{
return this.authHttpService.get(this.baseApiUrl + 'audience/all').map(res => res.json());
}
addAudience(model: AddDeleteAudienceModel) {
return this.authHttpService.post(this.baseApiUrl + 'audience', JSON.stringify(model)).map(res => res.json());
}
deleteAudience(model: AddDeleteAudienceModel) {
return this.authHttpService.post(this.baseApiUrl + 'audience/delete', JSON.stringify(model)).map(res => res.json());
}
}