I am trying to make a POST request via an absolute URL to a Spring (Basic authentication) secured Rest API.
Having read that Angular omits inserting the X-XSRF-TOKEN into the request header automatically for absolute urls, I tried to implement an HttpInterceptor to add the token in.
In my original /signin POST request, I create the necessary authorization: Basic header to ensure Spring authenticates the request.
The response header returned contains the expected set-cookie token:
Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755; Path=/
However, in my custom interceptor class when I try to obtain the token from the injected HttpXsrfTokenExtractor for the next request, it returns null.
Here is the code for my interceptor class:
import {Injectable, Inject} from '#angular/core';
import { Observable } from 'rxjs/Rx';
import {HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler,
HttpEvent} from '#angular/common/http';
#Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestMethod: string = req.method;
requestMethod = requestMethod.toLowerCase();
if (requestMethod && (requestMethod === 'post' || requestMethod === 'delete' || requestMethod === 'put' )) {
const headerName = 'X-XSRF-TOKEN';
let token = this.tokenExtractor.getToken() as string;
if (token !== null && !req.headers.has(headerName)) {
req = req.clone({ headers: req.headers.set(headerName, token) });
}
}
return next.handle(req);
}
}
tokenExtractor.getToken() returns null in the above code. I expected it to return the token from Spring (Set-Cookie) response header of my previous /signin request.
I read this related post for creating the interceptor: angular4 httpclient csrf does not send x-xsrf-token
But I wasn't able to find much documentation for HttpXsrfTokenExtractor other than this:
https://angular.io/api/common/http/HttpXsrfTokenExtractor
Question: Why is HttpXsrfTokenExtractor.getToken() returning null?
In addition I added the interceptor class as a provider to the app.module.
Here is my app.module.ts:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '#angular/core';
import { LocationStrategy, HashLocationStrategy, APP_BASE_HREF } from '#angular/common';
import { BrowserModule } from '#angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { HttpClientModule, HttpClientXsrfModule } from '#angular/common/http';
import { HTTP_INTERCEPTORS } from '#angular/common/http';
import { AlertModule } from 'ng2-bootstrap';
import { routing, appRouterProviders } from './app.routing';
import { HttpXsrfInterceptor } from './httpxrsf.interceptor';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './registration/register.component';
import { HomeComponent } from './home/home.component';
#NgModule({
declarations: [AppComponent,
LoginComponent,
RegisterComponent,
HomeComponent],
imports: [BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
HttpClientXsrfModule, // Adds xsrf support
AlertModule.forRoot(),
routing],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
appRouterProviders,
[{provide: APP_BASE_HREF, useValue: '/'}],
[{provide: LocationStrategy, useClass: HashLocationStrategy}],
[{provide: HTTP_INTERCEPTORS, useClass: HttpXsrfInterceptor, multi: true }]
],
bootstrap: [AppComponent]
})
export class AppModule {}
Another thing to note is that I am running my Angular front end on Node.js from localhost:3000 and my Spring Rest back-end from localhost:8080.
They are on different ports, and so the reason for making http requests with absolute urls. Would the browser prevent a Set-Cookie working when it comes from a response for a request on a different domain?
Could I be missing anything else?
Thank you for any help.
----------------------------------------------
[Updated 7th Jan 2018]
FIX
I use Webpack dev server to serve the Angular code. I worked around the issue by configuring a proxy for urls that point to my back-end Rest API.
This means all requests made from the browser now only be to the dev server at port 3000, even for the Rest API calls.
When the webpack dev server sees any request urls with the configured pattern (E.g /api/...), it replaces with calls to the back-end server on http://localhost:8080 (in my case).
This is my what I added to the devServer section of my webpack.dev.js file:
proxy: {
'/api': {
'target': 'http://localhost:8080',
'pathRewrite': {'^/api' : ''}
//Omits /api from the actual request. E.g. http://localhost:8080/api/adduser -> http://localhost:8080/adduser
}
}
With this set up, I no longer make cross-domain (cross-origin) requests from the Angular code or use absolute URLs anymore. This hugely simplifies things as I am not fighting the Angular XSRF (CSRF) mechanism anymore. It just works by default. I also do not need to use an HttpInterceptor to manually insert the X-XSRF-TOKEN in either.
The added benefit of setting up a dev server proxy is that client requests are no longer absolute, so I do not need to change all the Rest API calls for production.
I hope this is useful for anyone who is suffering the same problem/understanding.
Webpack dev server proxy documentation ref:
https://webpack.js.org/configuration/dev-server/#devserver-proxy
Since you are using different ports (3000 & 8080), you are making a cross-origin request, so you will not be able to read the cookie in the client sent from the server. If you want to separate your client and server in this way, you need to use a proxy so the client and server applications are served from the same protocol (http/https), domain, and port. If you are using Spring Boot I would suggest you look at Spring Cloud Netflix, specifically Zuul (https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_router_and_filter_zuul).
Related
I am pretty new to developing REST APIs using the NestJS framework (started using it last week).
For Authorization purposes I want to implement a CanActivate Guard to my app. This guard simply looks for an authorization header to extract the jwt. Furthermore the user needs to be fetched via a service to get its role and check for the required permission. Additionally there is a #Permission decorator, which is used on protected routes, that takes in a permission string.
This decorator file looks like this:
export const PERMISSION_KEY = "requiredPermission"
export const Permission = (permission: string) => {
return SetMetadata(PERMISSION_KEY, permission);
}
But I am experiencing strange behaviour: Only injecting the Reflector (so I can look up the required permission from the route) is working fine. When now trying to inject a service, let's say AuthService, the constructor of the AuthenticationGuard isn't even called, so the service results in undefined. Event the reflector instance is undefined, though it worked before. Here is how it looks in my authentication.guard.ts:
#Injectable()
export class AuthenticationGuard implements CanActivate {
constructor(
private reflector: Reflector,
private authService: AuthService
) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
// ... do some things to extract jwt from header ...
console.log(this.authService); // Results in undefined
// This line does not work because reflector also is undefined.
const requiredPermission = this.reflector.get<string>(PERMISSION_KEY, context.getHandler());
console.log(requiredPermission);
}
}
This guard is imported as a provider in a feature module called AuthModule:
#Module({
imports: [
JwtModule.register({
secret: "EXTREMELY_SECRET"
})
],
providers: [
AuthService,
{
provide: APP_GUARD,
useClass: AuthenticationGuard
}
],
controllers: [ AuthController ],
exports: [
AuthService
]
})
export class AuthModule {}
Now when removing the service from dependency injection and make a console.log, I can see that it gets instantiated as the first dependency of the whole app. Does this maybe cause the service to fail injecting? How would I possibly change fix that?
Or does injecting services not work for guards in general? I think this is not the problem.
Maybe someone can help me with this problem and give me some advice on to fix this issue.
Thank you very kindly in advance for your support!
I am trying to port the code of an Angular 2 app to Angular Dart, so that I can reuse the business logic in Flutter.
My app makes use of HttpInterceptors for Error handling and server authorization.
In typescript I would inject a simple service :
#Injectable({
providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return;
}
constructor(private auth: AuthService) { }
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {}
}
But the Dart API does not seem to have an HttpInterceptor class. Does one have to extend the HttpClient class in order to do so ?
I have looked at this S.O question but it dates back from 5 years ago, the way to do it has probably changed quite a bit in the meantime.
Turns out there are a couple ways you can achieve this in Angular Dart / Flutter.
Some third party libraries built on top of dart http provide convenience methods for Http interception (DIO, Http Interceptor). However, both handle interception at the client level.
I decided to go with the Dart Http library, using the recommended method :
class UserAgentClient extends http.BaseClient {
final String userAgent;
final http.Client _inner;
UserAgentClient(this.userAgent, this._inner);
Future<StreamedResponse> send(BaseRequest request) {
request.headers['user-agent'] = userAgent;
return _inner.send(request);
}
}
I have angular application(angular 6) which implemented keycloakAuthentication ,if user is not authenticated angular application redirect user to this service page(third party), when user successfully authenticated he will redirect to our angular website .
Now i am facing issue with http protocol if user type http://portal.cloud.com they will redirect to keycloakAuthentication page but redirect url(http://portal.cloud.com) is invalid because it is authenticating only https url ,how i can modify url in this case means if user type http://portal.cloud.com application will automatically convert it into https://portal.cloud.com and send it to keycloakAuthentication page.
I tried some solution like implemented authguard as below:
import {Injectable, isDevMode} from '#angular/core';
import {CanActivate} from '#angular/router';
#Injectable()
export class IsSecureGuard implements CanActivate {
canActivate(): boolean {
if ((isDevMode()) && (location.protocol !== 'https:')) {
location.href = 'https:' + window.location.href.substring(window.location.protocol.length);
return false;
}
return true;
}
}
but it is not working as expected please suggests some solution to modify url on load.
You can simplify your implementation by resolving the redirect to HTTPS before the user reaches your application, by setting up NGINX to do this for example:
See here how to: https://serverfault.com/questions/67316/in-nginx-how-can-i-rewrite-all-http-requests-to-https-while-maintaining-sub-dom
In angular2 app, we need to call a lot of 3rd party restful APIs. Any idea how to organise these APIs, so we can centralize the management of these APIs (maybe in a config file)?
For example, in angular2 tutorial
https://angular.io/docs/ts/latest/guide/server-communication.html#!#fetch-data
imagine I have 20 services, every one of them will have a few functions inside to use http get, post accessing a bunch of 3rd party restful APIs. So I want to put all these API links in a centralize place, later if there is any change from 3rd party API providers, I can change without going into my services ts files?
Updated
config-values.ts
import {OpaqueToken} from 'angular2/core';
export const API_LOGIN = new OpaqueToken('API_LOGIN');
main.ts to bootstrap
...
import {API_LOGIN} from './app/shared/services/config-values';
bootstrap(AppComponent, [
ROUTER_PROVIDERS,HTTP_PROVIDERS,
provide(API_LOGIN, {useValue: 'http:bar.com/somepath'}),
provide(PLATFORM_DIRECTIVES, {useValue: [ROUTER_DIRECTIVES], multi:true}),
provide(APP_BASE_HREF, { useValue: '<%= APP_BASE %>' })
]);
In login.service.js which calls it
...
import {API_LOGIN} from 'config-values.ts';
#Injectable()
export class LoginService {
private loggedIn = false;
constructor(#Inject(API_LOGIN) private apiUrl:string,private http:Http) {
this.loggedIn = !!localStorage.getItem('auth_token');
}
}
I would create a typescript file containing OpaqueTokenss and pass them using DI
config-values.ts
export const API_FOO_URL = CONST_EXPR(new OpaqueToken('API Foo URL'));
export const API_BAR_URL = CONST_EXPR(new OpaqueToken('API Foo URL'));
...
export const API_URLS = CONST_EXPR([
provide(API_FOO_URL, {useValue: 'http:foo.com/somepath'}),
provide(API_BAR_URL, {useValue: 'http:bar.com/somepath'}),
...
]);
main.ts
import {API_URLS} from 'config-values.ts';
bootstrap(AppComponent, [API_URLS, ...]);
foo.service.ts
import {API_FOO_URL} from 'config-values.ts';
#Injectable()
export class FooService {
constructor(#Inject(API_FOO_URL) private apiUrl:string) {}
}
I am trying to wire up a basic Angular2 app that uses the Http service. (Most of the tutorials I've seen do this by having a Component consume the Http service, which seems wrong unless the basic philosophy of thin controllers has changed – but that's a different question.)
I would like to create a service that uses Angular's Http service. But I can't figure out how to inject the Http service other than this:
boot.ts:
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
import {HTTP_PROVIDERS } from 'angular2/http';
bootstrap(AppComponent, [HTTP_PROVIDERS]);
myService.ts:
import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
#Injectable()
export class aService{
constructor(http:Http){
}
/** do some stuff *//
}
This works, but it seem very wrong to require the user of the service to know the service's dependencies and be required to inject them into the bootstrap process. It seems like there should be a way to directly hand a providers array to a service the same way you can a component, but I can't find it. Am I just missing something?
Update
This way if a parent injector provides an implementation for OtherService this one is used, otherwise OtherServiceImpl is used (default).
#Injectable()
class SomeService {
OtherService _other;
SomeService(Injector injector) {
_other = injector.getOptional(OtherService);
if (_other == null) {
_other = injector.resolveAndCreateChild([
provide(OtherService, useClass: OtherServiceImpl)
]).get(OtherService);
}
_other.doSomething();
}
}
If you provide another one like
bootstrap(AppElement, [
provide(OtherService, useClass: OtherServiceImpl2)
]);
OtherServiceImpl2 is used.
See also https://github.com/angular/angular/issues/5622
Original
You could just make the http service optional (using the #Optional() annotation) and if none is provided just create an instance inside the constructor with new Http().
This way the user doesn't need to know about the services dependencies, but is able to pass alternative implementations if necessary (for example for testing).
If creating the dependeny inside the service requires DI itself, you can inject an injector and use it to get dependencies.
See also optional dependencies in http://blog.thoughtram.io/angular/2015/05/18/dependency-injection-in-angular-2.html
What also could work (not tried myself yet) is just to create a child injector and instruct it to skip self
From the SkipSelfMetadata documentation
class Dependency {
}
#Injectable()
class NeedsDependency {
dependency;
constructor(#SkipSelf() dependency:Dependency) {
this.dependency = dependency;
}
}
var parent = Injector.resolveAndCreate([Dependency]);
var child = parent.resolveAndCreateChild([NeedsDependency]);
expect(child.get(NeedsDependency).dependency instanceof Depedency).toBe(true);
var inj = Injector.resolveAndCreate([Dependency, NeedsDependency]);
expect(() => inj.get(NeedsDependency)).toThrowError();
I don't know yet if this still resolves from "self" if parent can't provide the requested type.