Skip to content

Instantly share code, notes, and snippets.

@ejwinter
Last active June 27, 2019 12:09
Show Gist options
  • Save ejwinter/44438ada55b7dd7e0ec95ac2433b43b6 to your computer and use it in GitHub Desktop.
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.
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);
}
}));
}
}
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.');
};
}
@lonix1
Copy link

lonix1 commented Jun 27, 2019

Could you please post Credentials ("app/shared/security/credentials")?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment