@auth0/angular-jwt
Version:
JSON Web Token helper library for Angular
249 lines (241 loc) • 8.76 kB
JavaScript
import { InjectionToken, Injectable, Inject, NgModule, Optional, SkipSelf } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { mergeMap } from 'rxjs/operators';
import { from } from 'rxjs';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
const JWT_OPTIONS = new InjectionToken('JWT_OPTIONS');
// tslint:disable:no-bitwise
class JwtHelperService {
constructor(config = null) {
this.tokenGetter = (config && config.tokenGetter) || function () { };
}
urlBase64Decode(str) {
let output = str.replace(/-/g, "+").replace(/_/g, "/");
switch (output.length % 4) {
case 0: {
break;
}
case 2: {
output += "==";
break;
}
case 3: {
output += "=";
break;
}
default: {
throw new Error("Illegal base64url string!");
}
}
return this.b64DecodeUnicode(output);
}
// credits for decoder goes to https://github.com/atk
b64decode(str) {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let output = "";
str = String(str).replace(/=+$/, "");
if (str.length % 4 === 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (
// initialize result and counters
let bc = 0, bs, buffer, idx = 0;
// get next character
(buffer = str.charAt(idx++));
// character found in table? initialize bit storage and add its ascii value;
~buffer &&
((bs = bc % 4 ? bs * 64 + buffer : buffer),
// and if not first of each 4 characters,
// convert the first 8 bits to one ascii character
bc++ % 4)
? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6))))
: 0) {
// try to find character in table (0-63, not found => -1)
buffer = chars.indexOf(buffer);
}
return output;
}
b64DecodeUnicode(str) {
return decodeURIComponent(Array.prototype.map
.call(this.b64decode(str), (c) => {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join(""));
}
decodeToken(token = this.tokenGetter()) {
if (!token || token === "") {
return null;
}
const parts = token.split(".");
if (parts.length !== 3) {
throw new Error("The inspected token doesn't appear to be a JWT. Check to make sure it has three parts and see https://jwt.io for more.");
}
const decoded = this.urlBase64Decode(parts[1]);
if (!decoded) {
throw new Error("Cannot decode the token.");
}
return JSON.parse(decoded);
}
getTokenExpirationDate(token = this.tokenGetter()) {
let decoded;
decoded = this.decodeToken(token);
if (!decoded || !decoded.hasOwnProperty("exp")) {
return null;
}
const date = new Date(0);
date.setUTCSeconds(decoded.exp);
return date;
}
isTokenExpired(token = this.tokenGetter(), offsetSeconds) {
if (!token || token === "") {
return true;
}
const date = this.getTokenExpirationDate(token);
offsetSeconds = offsetSeconds || 0;
if (date === null) {
return false;
}
return !(date.valueOf() > new Date().valueOf() + offsetSeconds * 1000);
}
getAuthScheme(authScheme, request) {
if (typeof authScheme === "function") {
return authScheme(request);
}
return authScheme;
}
}
JwtHelperService.decorators = [
{ type: Injectable }
];
JwtHelperService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [JWT_OPTIONS,] }] }
];
class JwtInterceptor {
constructor(config, jwtHelper, document) {
this.jwtHelper = jwtHelper;
this.document = document;
this.standardPorts = ["80", "443"];
this.tokenGetter = config.tokenGetter;
this.headerName = config.headerName || "Authorization";
this.authScheme =
config.authScheme || config.authScheme === ""
? config.authScheme
: "Bearer ";
this.allowedDomains = config.allowedDomains || [];
this.disallowedRoutes = config.disallowedRoutes || [];
this.throwNoTokenError = config.throwNoTokenError || false;
this.skipWhenExpired = config.skipWhenExpired;
}
isAllowedDomain(request) {
const requestUrl = new URL(request.url, this.document.location.origin);
// If the host equals the current window origin,
// the domain is allowed by default
if (requestUrl.host === this.document.location.host) {
return true;
}
// If not the current domain, check the allowed list
const hostName = `${requestUrl.hostname}${requestUrl.port && !this.standardPorts.includes(requestUrl.port)
? ":" + requestUrl.port
: ""}`;
return (this.allowedDomains.findIndex((domain) => typeof domain === "string"
? domain === hostName
: domain instanceof RegExp
? domain.test(hostName)
: false) > -1);
}
isDisallowedRoute(request) {
const requestedUrl = new URL(request.url, this.document.location.origin);
return (this.disallowedRoutes.findIndex((route) => {
if (typeof route === "string") {
const parsedRoute = new URL(route, this.document.location.origin);
return (parsedRoute.hostname === requestedUrl.hostname &&
parsedRoute.pathname === requestedUrl.pathname);
}
if (route instanceof RegExp) {
return route.test(request.url);
}
return false;
}) > -1);
}
handleInterception(token, request, next) {
const authScheme = this.jwtHelper.getAuthScheme(this.authScheme, request);
let tokenIsExpired = false;
if (!token && this.throwNoTokenError) {
throw new Error("Could not get token from tokenGetter function.");
}
if (this.skipWhenExpired) {
tokenIsExpired = token ? this.jwtHelper.isTokenExpired(token) : true;
}
if (token && tokenIsExpired && this.skipWhenExpired) {
request = request.clone();
}
else if (token) {
request = request.clone({
setHeaders: {
[this.headerName]: `${authScheme}${token}`,
},
});
}
return next.handle(request);
}
intercept(request, next) {
if (!this.isAllowedDomain(request) || this.isDisallowedRoute(request)) {
return next.handle(request);
}
const token = this.tokenGetter(request);
if (token instanceof Promise) {
return from(token).pipe(mergeMap((asyncToken) => {
return this.handleInterception(asyncToken, request, next);
}));
}
else {
return this.handleInterception(token, request, next);
}
}
}
JwtInterceptor.decorators = [
{ type: Injectable }
];
JwtInterceptor.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [JWT_OPTIONS,] }] },
{ type: JwtHelperService },
{ type: Document, decorators: [{ type: Inject, args: [DOCUMENT,] }] }
];
class JwtModule {
constructor(parentModule) {
if (parentModule) {
throw new Error("JwtModule is already loaded. It should only be imported in your application's main module.");
}
}
static forRoot(options) {
return {
ngModule: JwtModule,
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: JwtInterceptor,
multi: true,
},
options.jwtOptionsProvider || {
provide: JWT_OPTIONS,
useValue: options.config,
},
JwtHelperService,
],
};
}
}
JwtModule.decorators = [
{ type: NgModule }
];
JwtModule.ctorParameters = () => [
{ type: JwtModule, decorators: [{ type: Optional }, { type: SkipSelf }] }
];
/*
* Public API Surface of angular-jwt
*/
/**
* Generated bundle index. Do not edit.
*/
export { JWT_OPTIONS, JwtHelperService, JwtInterceptor, JwtModule };
//# sourceMappingURL=auth0-angular-jwt.js.map