I use Angular2, Angular-cli, Spring Boot 1.4.0 and jwt. When I sign in my Angular2 client I can not get jwt token.
My security config is:
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // disable csrf for our requests.
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api/user/signup").permitAll()
.antMatchers(HttpMethod.POST, "/api/user/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/api/user/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
.permitAll().and().csrf().disable();
}
}
My TokenAuthenticationService is :
public class TokenAuthenticationService {
private final long EXPIRATIONTIME = 1000 * 60 * 60 * 24 * 10; // 10 days
private final String secret = "ThisIsASecret";
private final String tokenPrefix = "Bearer";
private final String headerString = "Authorization";
public void addAuthentication(HttpServletResponse response, String username)
{
// We generate a token now.
String JWT = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader(headerString, tokenPrefix + " "+ JWT);
response.getHeaderNames().stream()
.forEach(System.out::println);
}
}
When I send sign in request with postman, I recieve response like this:
But I send sign in request my Angular2 application I can not recieve response header named "Authorization" custom header. My response object is like this:
But I look browser console I see my costum header "Authorization".
My Angular2 code is:
@Injectable()
export class LoginService {
private authEvents: Subject<AuthEvent>;
private cred: AccountCredentials;
constructor(private http: JsonHttpService ){
this.authEvents = new Subject<AuthEvent>();
this.cred = new AccountCredentials();
}
login(email: string, password: string) {
this.cred.password = password;
this.cred.username = email;
return this.http.post('http://localhost:9090/api/user/login', this.cred)
.do((resp: Response) => {
localStorage.setItem('jwt', resp.headers.get('Authorization'));
this.authEvents.next(new DidLogin());
});
}
logout(): void {
localStorage.removeItem('jwt');
this.authEvents.next(new DidLogout());
}
isSignedIn(): boolean {
return localStorage.getItem('jwt') !== null;
}
}
export class DidLogin {
}
export class DidLogout {
}
export type AuthEvent = DidLogin | DidLogout;
And My JsonHttpService is:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import {
Http,
RequestOptionsArgs,
RequestOptions,
Response,
Headers
} from '@angular/http';
const mergeAuthToken = (options: RequestOptionsArgs = {}) => {
let newOptions = new RequestOptions({}).merge(options);
let newHeaders = new Headers(newOptions.headers);
const jwt = localStorage.getItem('jwt');
if (jwt && jwt !== 'null') {
newHeaders.set('Authorization', jwt);
}
newHeaders.set('content-type', 'application/x-www-form-urlencoded; charset=utf-8');
// newHeaders.set('Access-Control-Allow-Origin', '*');
newOptions.headers = newHeaders;
return newOptions;
};
@Injectable()
export class JsonHttpService {
constructor(private http: Http) { }
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.get(url, mergeAuthToken(options));
}
post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
return this.http.post(url, body, mergeAuthToken(options));
}
put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
return this.http.put(url, body, mergeAuthToken(options));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.delete(url, mergeAuthToken(options));
}
patch(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
return this.http.patch(url, body, mergeAuthToken(options));
}
head(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.http.head(url, mergeAuthToken(options));
}
}
So why I can not recieve my jwt token and add my browser localStorage?
The Best way to solve your problem would be to add cors configurations in your application as described here https://spring.io/blog/2015/06/08/cors-support-in-spring-framework. You can also do it as follows:
Are you running your Angular2 app and springboot on a different port? If so, have you enable CORS in your springboot application?
Add
withCredentials: true
to your Angualr2 post headerFor more springboot JWT work with Angular, checkout Springboot JWT Starter
I had similar issue with Angular 6 and couldn't find solution for few days. After googling, frustrating, thinking and who knows what else I found the answer :)
At the end I had to switch to pure javascript and this solution works for me:
http.getAllResponseHeaders() returns all headers similar like in web browser. Hope this will help you too.
I found well written post about CORS, which can cause troubles, on https://www.html5rocks.com/en/tutorials/cors/
Also as @evans-m said headers have to be exposed.
I am not still sure is it problem with Angular, browser or maybe even spring?!
The browser does not expose custom headers to the app by default.
You will need the following header in your Backend Cors config
Note that even if the headers are present in the dev console your app can't read them if they are not exposed by you server application.