Last active
June 27, 2019 12:09
-
-
Save ejwinter/44438ada55b7dd7e0ec95ac2433b43b6 to your computer and use it in GitHub Desktop.
A service to perform login and logout and an http interceptor to use the users token to authenticate each request.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest,} from "@angular/common/http"; | |
import {Injectable} from "@angular/core"; | |
import {Credentials} from "app/shared/security/credentials"; | |
import {Observable, throwError} from "rxjs"; | |
import {concatMap, take, tap} from "rxjs/operators"; | |
import {CredentialService} from "app/shared/service/credential.service"; | |
@Injectable() | |
export class AuthHttpInterceptor implements HttpInterceptor { | |
private credentials: Credentials; | |
constructor( | |
private credentialService: CredentialService, | |
) { | |
credentialService.subscribeToCredentialChanges({ | |
next: (creds) => { | |
this.credentials = creds; | |
}, | |
}); | |
} | |
public intercept(req: HttpRequest<any>, requestHandler: HttpHandler): Observable<HttpEvent<any>> { | |
if (req.url.includes("/api/")) { | |
if (this.credentials && this.credentials.token) { | |
const clonedRequest = req.clone({setHeaders: {Authorization: `Bearer ${this.credentials.token}`}}); | |
return requestHandler.handle(clonedRequest) | |
.pipe(tap({ | |
error: (err) => { | |
if (err instanceof HttpErrorResponse) { | |
if (err.status === 401) { | |
this.credentialService.logout(); | |
} | |
} | |
throwError(err); | |
}, | |
})); | |
} else { | |
return this.createChainAfterCredentialChanges(req, requestHandler); | |
} | |
} | |
return requestHandler.handle(req); | |
} | |
private createChainAfterCredentialChanges(req: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>> { | |
console.debug("Saving this request until a user is authenticated.", req); | |
// THIS is the key spot that allows us to return an observable for each request that | |
// will later be fullfilled when the credentials are set. | |
return this.credentialService.credentials$ | |
.pipe(take(1)) | |
.pipe(concatMap((creds) => { | |
if (creds && creds.token) { | |
const clonedRequest = req.clone({setHeaders: {Authorization: `Bearer ${creds.token}`}}); | |
console.debug("Full filling a saved request", clonedRequest); | |
return next.handle(clonedRequest); | |
} else { | |
console.debug("The credentials were reset to undefined so we were unable to complete queued requests.", req); | |
return this.createChainAfterCredentialChanges(req, next); | |
} | |
})); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {HttpClient, HttpErrorResponse,} from "@angular/common/http"; | |
import {Injectable} from "@angular/core"; | |
import {baseUrl} from "app/shared/BaseUrlConfig"; | |
import {Credentials} from "app/shared/security/credentials"; | |
import {PartialObserver, Subject, Subscription, throwError} from "rxjs"; | |
import {catchError, take} from "rxjs/operators"; | |
import {ActivatedRoute, Router, RouterStateSnapshot} from "@angular/router"; | |
@Injectable({ | |
providedIn: "root" | |
}) | |
export class CredentialService { | |
public credentials$: Subject<Credentials> = new Subject(); | |
private incomingUrl: string; | |
constructor( | |
private http: HttpClient, | |
private router: Router, | |
) {} | |
public init(){ | |
this.credentials$.subscribe((creds) => { | |
console.debug("changed creds: ", creds); | |
if (creds) { | |
if (this.incomingUrl) { | |
console.debug(`Returning to '${this.incomingUrl}'`); | |
this.router.navigateByUrl(this.incomingUrl); | |
this.incomingUrl = undefined; | |
} | |
// this.messagingService.displayInfoMessage("Welcome", creds.name); | |
if (creds.token) { | |
localStorage.setItem("token", creds.token); | |
} else { | |
localStorage.removeItem("token"); | |
} | |
} else { | |
this.incomingUrl = this.router.routerState.snapshot.url || '/'; | |
console.log(`Storing url: '${this.incomingUrl}'`); | |
this.router.navigate(["login"]); | |
} | |
}); | |
if (localStorage.getItem("token")) { | |
this.http.get(`${baseUrl}/security/credentials/${localStorage.getItem("token")}`) | |
.subscribe({ | |
next: (creds: Credentials) => { | |
console.debug("Logged in from saved token."); | |
this.credentials$.next(creds); | |
}, | |
error: (err) => { | |
if (err.status && err.status === 404) { | |
// likely a 404 meaning the token session was not found | |
console.debug("Credentials have expired, log in again."); | |
this.logout(); | |
} else { | |
throwError(err); | |
} | |
}, | |
}); | |
} else { | |
this.credentials$.next(undefined); | |
} | |
} | |
public login(credentials: Credentials, callback) { | |
this.http.post(`${baseUrl}/security/login`, credentials) | |
.pipe(take(1)) | |
.pipe(catchError(this.handleError)) | |
.subscribe((creds: Credentials) => { | |
this.credentials$.next(creds); | |
callback(creds); | |
}); | |
} | |
public logout(): void { | |
localStorage.removeItem("token"); | |
this.credentials$.next(undefined); | |
} | |
public subscribeToCredentialChanges(obs: PartialObserver<Credentials>): Subscription { | |
return this.credentials$.subscribe(obs); | |
} | |
private handleError(error: HttpErrorResponse) { | |
if (error.error instanceof ErrorEvent) { | |
// A client-side or network error occurred. Handle it accordingly. | |
console.error("An error occurred:", error.error.message); | |
} else { | |
// The backend returned an unsuccessful response code. | |
// The response body may contain clues as to what went wrong, | |
console.error(error); | |
if(error.status) { | |
console.error(`Backend returned code ${error.status}, body was: ${error.error}`); | |
if(error.status === 401){ | |
this.logout(); | |
} | |
} | |
} | |
// return an observable with a user-facing error message | |
return throwError( | |
'Something bad happened; please try again later.'); | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Could you please post
Credentials
("app/shared/security/credentials"
)?