@glamtime/oauth-oidc-client
Version:
Secure your Angular app using the latest standards for OpenID Connect & OAuth2. Provides support for token refresh, all modern OIDC Identity Providers and more.
421 lines (411 loc) • 18 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Inject, PLATFORM_ID, NgModule } from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { from, BehaviorSubject, map, switchMap, tap, of } from 'rxjs';
import * as i1 from '@angular/common/http';
import { HttpHeaders, HttpParams } from '@angular/common/http';
class OAuthConfig {
constructor() {
// The URL for your Okta organization or an Okta authentication server.
// This option is required
this.issuer = 'https://id.glamtime.com.cn';
// Client id pre-registered for the OIDC authentication flow.
this.clientId = '';
// Used in authorization and interaction code flows by server-side web applications to obtain OAuth tokens.
// In a production application, this value should never be visible on the client side.
this.clientSecret = '';
// The url that is redirected to when using token.getWithRedirect.
// If no redirectUri is provided, defaults to the current origin (window.location.origin).
this.redirectUri = window.location.origin;
// The url that is redirected to when using token.getWithRedirect.
// If no redirectUri is provided, defaults to the current origin (window.location.origin).
this.postLogoutRedirectUri = window.location.origin;
// Specify what information to make available in the returned id_token or access_token.
// For OIDC, you must include openid as one of the scopes.
// Defaults to ['openid', 'email'].
this.scopes = ['openid', 'profile', 'email'];
// Array of secure URLs on which the token should be sent if the interceptor is added to the `HTTP_INTERCEPTORS`.
this.resourceServers = [];
}
}
const OAUTH_CONFIG = new InjectionToken('OAUTH_CONFIG');
class OAuthStorageService {
constructor() {
this._storage = localStorage;
}
get state() {
return this._getItem('state');
}
set state(code) {
if (code) {
this._setItem('state', code);
}
else {
this._removeItem('state');
}
}
get codeVerifier() {
return this._getItem('codeVerifier');
}
set codeVerifier(code) {
if (code) {
this._setItem('codeVerifier', code);
}
else {
this._removeItem('codeVerifier');
}
}
get accessToken() {
return this._getItem('accessToken');
}
set accessToken(token) {
if (token) {
this._setItem('accessToken', token);
}
else {
this._removeItem('accessToken');
}
}
get idToken() {
return this._getItem('idToken');
}
set idToken(token) {
if (token) {
this._setItem('idToken', token);
}
else {
this._removeItem('idToken');
}
}
get refreshToken() {
return this._getItem('refreshToken');
}
set refreshToken(token) {
if (token) {
this._setItem('refreshToken', token);
}
else {
this._removeItem('refreshToken');
}
}
resetAuthState() {
this._removeItem('state');
this._removeItem('codeVerifier');
this._removeItem('accessToken');
this._removeItem('idToken');
this._removeItem('refreshToken');
this._removeItem('jwtKeys');
}
_getItem(key) {
return this._storage.getItem(key);
}
_setItem(key, value) {
this._storage.setItem(key, value);
}
_removeItem(key) {
this._storage.removeItem(key);
}
_clear() {
this._storage.clear();
}
}
OAuthStorageService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
OAuthStorageService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthStorageService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthStorageService, decorators: [{
type: Injectable
}], ctorParameters: function () { return []; } });
class OAuthCryptoService {
constructor(_document) {
this._document = _document;
}
generateState() {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const crypto = this._getCrypto();
if (crypto) {
let bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
result = Array.from(bytes, (v) => {
return characters[v % characters.length];
}).join('');
}
return result;
}
/**
* Generate code verifier
* See also: https://tools.ietf.org/html/rfc7636#section-4.1
*/
generatePKCECodeVerifier() {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
const crypto = this._getCrypto();
if (crypto) {
let bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
result = Array.from(bytes, (v) => {
return characters[v % characters.length];
}).join('');
}
return this._base64UrlEncode(result);
}
generatePKCECodeChallenge(codeVerifier) {
const crypto = this._getCrypto();
return from(new Promise((resolve, reject) => {
if (crypto && codeVerifier) {
const bytes = new Uint8Array(codeVerifier.length);
for (let i = 0; i < codeVerifier.length; i++) {
bytes[i] = codeVerifier.charCodeAt(i);
}
crypto.subtle.digest('SHA-256', bytes).then((buffer) => {
const bytesArray = new Uint8Array(buffer);
const s = Array.from(bytesArray, (v) => {
return String.fromCharCode(v);
}).join('');
resolve(this._base64UrlEncode(s));
});
}
else {
reject();
}
}));
}
_getCrypto() {
// support for IE, (window.crypto || window.msCrypto)
return this._document.defaultView.crypto || this._document.defaultView.msCrypto;
}
_base64UrlEncode(str) {
const base64 = self.btoa(str);
return base64
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
}
OAuthCryptoService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthCryptoService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
OAuthCryptoService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthCryptoService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthCryptoService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }]; } });
const OAUTH_WELL_KNOWN_SUFFIX = `/.well-known/openid-configuration`;
class OAuthService {
constructor(_zone, _httpClient, _storageService, _cryptoService, _document, _platformId, config) {
this._zone = _zone;
this._httpClient = _httpClient;
this._storageService = _storageService;
this._cryptoService = _cryptoService;
this._document = _document;
this._platformId = _platformId;
this._config = new OAuthConfig();
this._openIDConfiguration = null;
this._jwtKeys = { keys: [] };
this._userInfo = null;
this.authenticated$ = new BehaviorSubject(false);
this._config = { ...(new OAuthConfig()), ...config };
}
get accessToken() {
return this._storageService.accessToken;
}
get resourceServers() {
return this._config.resourceServers;
}
checkAuth() {
return this._fetchOpenIdConfiguration().pipe(map(() => !!(this._storageService.refreshToken && this._storageService.accessToken)), switchMap((result) => {
if (result) {
this.authenticated$.next(true);
return this._fetchRefreshToken().pipe(switchMap(() => this._fetchUserProfile()));
}
else {
const currentUrl = this._document.defaultView.location.toString();
const parsedUrl = new URL(currentUrl);
const urlParams = new URLSearchParams(parsedUrl.search);
if (urlParams.has('code') && urlParams.has('state')) {
const authorizationCode = urlParams.get('code');
const state = urlParams.get('state');
if (state === this._storageService.state) {
return this._fetchToken(authorizationCode).pipe(switchMap(() => this._fetchUserProfile()), tap(() => {
this.authenticated$.next(true);
}));
}
else {
throw new Error('');
}
}
}
this.authenticated$.next(false);
return of(false);
}));
}
login() {
this._storageService.resetAuthState();
return this._fetchOpenIdConfiguration().pipe(map(() => {
this._storageService.state = this._cryptoService.generateState();
this._storageService.codeVerifier = this._cryptoService.generatePKCECodeVerifier();
return this._storageService.codeVerifier;
}), switchMap((codeVerifier) => this._cryptoService.generatePKCECodeChallenge(codeVerifier)), map((codeChallenge) => {
this._redirectTo(this._createLoginUrl(codeChallenge));
return true;
}));
}
logout() {
this._storageService.resetAuthState();
return this._fetchOpenIdConfiguration().pipe(switchMap(() => {
const url = this._openIDConfiguration?.revocation_endpoint;
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
headers = headers.set('Authorization', this._encodeClientCredentials());
let params = new HttpParams()
.set('token', this._storageService.accessToken);
return this._httpClient.post(url, params, { headers: headers });
}), tap(() => this.authenticated$.next(false)));
}
// Fetch the metadata from openid-configuration
_fetchOpenIdConfiguration() {
if (this._openIDConfiguration) {
return of(this._openIDConfiguration);
}
const url = this._config.issuer + OAUTH_WELL_KNOWN_SUFFIX;
return this._httpClient.get(url).pipe(tap((data) => {
this._openIDConfiguration = data;
}), switchMap(_ => {
return this._httpClient.get(this._openIDConfiguration.jwks_uri).pipe(tap((data) => {
this._jwtKeys = data;
}));
}), map(_ => {
return this._openIDConfiguration;
}));
}
_fetchToken(authorizationCode) {
const url = this._openIDConfiguration.token_endpoint;
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
headers = headers.set('Authorization', this._encodeClientCredentials());
let params = new HttpParams()
.set('grant_type', 'authorization_code')
.set('code', authorizationCode)
.set('redirect_uri', this._config.redirectUri);
if (this._storageService.codeVerifier) {
params = params.set('code_verifier', this._storageService.codeVerifier);
}
return this._httpClient.post(url, params, { headers: headers }).pipe(tap((value) => {
this._storageService.accessToken = value.access_token;
this._storageService.refreshToken = value.refresh_token;
this._storageService.idToken = value.id_token;
}));
}
_fetchRefreshToken() {
const url = this._openIDConfiguration.token_endpoint;
let headers = new HttpHeaders();
headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
headers = headers.set('Authorization', this._encodeClientCredentials());
let params = new HttpParams()
.set('grant_type', 'refresh_token')
.set('refresh_token', this._storageService.refreshToken);
return this._httpClient.post(url, params, { headers: headers }).pipe(tap((value) => {
this._storageService.accessToken = value.access_token;
this._storageService.refreshToken = value.refresh_token;
this._storageService.idToken = value.id_token;
}));
}
_fetchUserProfile() {
const headers = new HttpHeaders().set('Authorization', 'Bearer ' + this._storageService.accessToken);
return this._httpClient.get(this._openIDConfiguration.userinfo_endpoint, { headers: headers }).pipe(tap((value) => {
this._userInfo = value;
}));
}
_createLoginUrl(codeChallenge) {
return this._openIDConfiguration.authorization_endpoint +
'?' +
'response_type=' +
encodeURIComponent('code') +
'&client_id=' +
encodeURIComponent(this._config.clientId) +
'&state=' +
encodeURIComponent(this._storageService.state) +
'&scope=' +
encodeURIComponent(this._config.scopes.join(' ')) +
'&code_challenge=' +
codeChallenge +
'&code_challenge_method=S256' +
'&redirect_uri=' +
encodeURIComponent(this._config.redirectUri);
}
_redirectTo(url) {
this._document.location.href = url;
}
_encodeClientCredentials() {
return 'Basic ' + self.btoa(`${this._config.clientId}:${this._config.clientSecret}`);
}
}
OAuthService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthService, deps: [{ token: i0.NgZone }, { token: i1.HttpClient }, { token: OAuthStorageService }, { token: OAuthCryptoService }, { token: DOCUMENT }, { token: PLATFORM_ID }, { token: OAUTH_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
OAuthService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthService });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthService, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i1.HttpClient }, { type: OAuthStorageService }, { type: OAuthCryptoService }, { type: undefined, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [OAUTH_CONFIG]
}] }]; } });
class OAuthInterceptor {
constructor(_oauthService) {
this._oauthService = _oauthService;
}
intercept(request, next) {
for (const url of this._oauthService.resourceServers) {
if (request.url.startsWith(url)) {
const token = this._oauthService.accessToken;
if (token) {
request = request.clone({
headers: request.headers.set('Authorization', 'Bearer ' + token)
});
}
break;
}
}
return next.handle(request);
}
}
OAuthInterceptor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthInterceptor, deps: [{ token: OAuthService }], target: i0.ɵɵFactoryTarget.Injectable });
OAuthInterceptor.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthInterceptor });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthInterceptor, decorators: [{
type: Injectable
}], ctorParameters: function () { return [{ type: OAuthService }]; } });
class OAuthModule {
static forRoot(config) {
return {
ngModule: OAuthModule,
providers: [
{ provide: OAUTH_CONFIG, useValue: config },
OAuthStorageService,
OAuthCryptoService,
OAuthService,
]
};
}
}
OAuthModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
OAuthModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.8", ngImport: i0, type: OAuthModule });
OAuthModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthModule });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.8", ngImport: i0, type: OAuthModule, decorators: [{
type: NgModule,
args: [{
declarations: [],
imports: [],
exports: []
}]
}] });
/*
* Public API Surface of oauth-oidc-client
*/
/**
* Generated bundle index. Do not edit.
*/
export { OAUTH_CONFIG, OAUTH_WELL_KNOWN_SUFFIX, OAuthConfig, OAuthCryptoService, OAuthInterceptor, OAuthModule, OAuthService, OAuthStorageService };
//# sourceMappingURL=glamtime-oauth-oidc-client.mjs.map