Skip to content

Instantly share code, notes, and snippets.

@paulrobello
Created September 18, 2021 17:02
Show Gist options
  • Save paulrobello/7fe1c88492b175563f7c660d6e3d4309 to your computer and use it in GitHub Desktop.
Save paulrobello/7fe1c88492b175563f7c660d6e3d4309 to your computer and use it in GitHub Desktop.
angular-oauth2-oidc auth interceptor with id token support
import { Injectable, ModuleWithProviders, NgModule, Optional } from '@angular/core';
import {
HTTP_INTERCEPTORS,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import {
OAuthNoopResourceServerErrorHandler,
OAuthResourceServerErrorHandler,
OAuthService, UrlHelperService
} from 'angular-oauth2-oidc';
import { Observable, of, merge } from 'rxjs';
import {
catchError,
filter,
map,
take,
mergeMap,
timeout
} from 'rxjs/operators';
export abstract class AppOAuthModuleConfig {
resourceServer?: AppOAuthResourceServerConfig;
}
export type SendTokenType = 'NONE' | 'AUTH' | 'ID';
export abstract class AppOAuthResourceServerConfig {
/**
* Urls for which calls should be intercepted.
* If there is an ResourceServerErrorHandler registered, it is used for them.
* If sendAccessToken is set to true, the access_token is send to them too.
*/
allowedUrls?: Array<string>;
sendToken: SendTokenType = 'NONE';
customUrlValidation?: (url: string) => boolean;
}
@Injectable({
providedIn: 'root'
})
export class AppOAuthInterceptor implements HttpInterceptor {
constructor(
private oAuthService: OAuthService,
private errorHandler: OAuthResourceServerErrorHandler,
@Optional() private moduleConfig: AppOAuthModuleConfig
) {
}
private checkUrl(url: string): boolean {
if (this.moduleConfig?.resourceServer?.customUrlValidation) {
return this.moduleConfig.resourceServer.customUrlValidation(url);
}
if (this.moduleConfig?.resourceServer?.allowedUrls) {
return !!this.moduleConfig.resourceServer.allowedUrls.find((u) =>
url.toLowerCase().startsWith(u.toLowerCase())
);
}
return true;
}
public intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const url = req.url.toLowerCase();
if (
!this.moduleConfig ||
!this.moduleConfig.resourceServer ||
!this.checkUrl(url)
) {
return next.handle(req);
}
const sendToken = this.moduleConfig.resourceServer.sendToken;
if (!sendToken || sendToken == 'NONE') {
return next
.handle(req)
.pipe(catchError((err) => this.errorHandler.handleError(err)));
}
const token = sendToken == 'AUTH' ? this.oAuthService.getAccessToken() : this.oAuthService.getIdToken();
return merge(
of(token).pipe(filter((token) => !!token)),
this.oAuthService.events.pipe(
filter((e) => e.type === 'token_received'),
timeout(this.oAuthService.waitForTokenInMsec || 0),
catchError((_) => of(null)), // timeout is not an error
map((_) => this.oAuthService.getAccessToken())
)
).pipe(
take(1),
mergeMap((token) => {
if (token) {
const header = 'Bearer ' + token;
const headers = req.headers.set('Authorization', header);
req = req.clone({headers});
}
return next
.handle(req)
.pipe(catchError((err) => this.errorHandler.handleError(err)));
})
);
}
}
@NgModule({})
export class AuthInterceptModule {
static forRoot(
config?: AppOAuthModuleConfig
): ModuleWithProviders<AuthInterceptModule> {
return {
ngModule: AuthInterceptModule,
providers: [
OAuthService,
UrlHelperService,
{provide: AppOAuthModuleConfig, useValue: config},
{
provide: OAuthResourceServerErrorHandler,
useClass: OAuthNoopResourceServerErrorHandler
},
{
provide: HTTP_INTERCEPTORS,
useClass: AppOAuthInterceptor,
multi: true
}]
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment