Skip to content

Instantly share code, notes, and snippets.

@leon
Last active November 30, 2021 07:58
Show Gist options
  • Save leon/d73a87e39441aebcb35ffa8b88441098 to your computer and use it in GitHub Desktop.
Save leon/d73a87e39441aebcb35ffa8b88441098 to your computer and use it in GitHub Desktop.
Angular 2 SecureHttp

When communicating with a backend we often need to add an authentication token and csrf-tokens.

Angular doesn't provide http interceptors as it did before, you instead need to create a subclass of http to get what you are looking for.

By using the SecureHttp service instead of the basic http service we can automatically add this to all outgoing requests.

As the backend often speaks json, this service also automatically converts post request body into json and converts response body's into json as well.

import {
Http,
ConnectionBackend,
Headers,
Request,
RequestOptions,
RequestOptionsArgs,
Response,
RequestMethod,
} from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
// A service that can get the logged in users jwt token as an observable
import { SecurityService } from './security.service';
// A service that handles cookies (angular2-cookie)
import { CookieService } from '../cookie';
/**
* Custom Http client that handles conversions to json, adds CSRF token, and jwt token and redirects to signin if token is missing
*/
export class SecureHttp extends Http {
constructor(
backend: ConnectionBackend,
defaultOptions: RequestOptions,
private securityService: SecurityService,
private cookieService: CookieService
) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<any> {
if (typeof url === 'string') {
return this.get(url, options); // Recursion: transform url from String to Request
}
return this.sendRequest(url, options);
}
get(url: string, options?: RequestOptionsArgs): Observable<any> {
return this.sendRequest({ method: RequestMethod.Get, url: url, body: '' }, options);
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<any> {
return this.sendRequest({ method: RequestMethod.Post, url: url, body: body }, options);
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<any> {
return this.sendRequest({ method: RequestMethod.Put, url: url, body: body }, options);
}
delete(url: string, options?: RequestOptionsArgs): Observable<any> {
return this.sendRequest({ method: RequestMethod.Delete, url: url, body: '' }, options);
}
patch(url: string, body: string, options?: RequestOptionsArgs): Observable<any> {
return this.sendRequest({ method: RequestMethod.Patch, url: url, body: body }, options);
}
head(url: string, options?: RequestOptionsArgs): Observable<any> {
return this.sendRequest({ method: RequestMethod.Head, url: url, body: '' }, options);
}
private sendRequest(requestOptionsArgs: RequestOptionsArgs, options?: RequestOptionsArgs): Observable<any> {
let requestOptions = new RequestOptions(requestOptionsArgs);
// Convert body to stringified json if it's not a string already
if (typeof requestOptions.body !== 'string') {
requestOptions.body = JSON.stringify(requestOptions.body);
}
// Get xsrf token from spring security cookie
// by adding .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
const csrfToken: string = this.cookieService.get('XSRF-TOKEN');
let baseOptions: RequestOptions = new RequestOptions({
headers: new Headers({
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'X-XSRF-TOKEN': csrfToken
})
});
return this.securityService.accessToken$.mergeMap(token => {
// If there is a token we add it to the baseOptions
if (token) {
baseOptions.headers.set('Authorization', 'Bearer ' + token);
}
// We create a request from the passed in method, url, body and merge our base options in there
let request = new Request(baseOptions.merge(requestOptions));
return super.request(request, options)
.map(res => res.json())
.catch(this.errorHandler);
});
}
private errorHandler(errorResponse: Response): Observable<any> | ErrorObservable {
if (errorResponse.status === 401) {
console.log('redirecting to login');
window.location.href = '/login';
return Observable.empty();
}
// If it's a serious problem we can log it to a service if we want to
if (errorResponse.status === 500) {
// this.errorReporter.logError(errorResponse);
}
console.error(errorResponse);
return Observable.throw(errorResponse.text().length > 0 ? errorResponse.json() : { status: 'error' });
}
}
@NgModule({
imports: [
HttpModule,
CookieModule,
StorageModule,
],
declarations: [
...DIRECTIVES,
...COMPONENTS,
],
exports: [
...DIRECTIVES,
]
})
export class SecurityModule {
// Only create on instance of these
static forRoot(): ModuleWithProviders {
return {
ngModule: SecurityModule,
providers: [
SecurityService,
{
provide: SecureHttp,
useFactory: secureHttpFactory,
deps: [XHRBackend, RequestOptions, SecurityService, CookieService]
}
]
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment