Reputation: 171
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?
Upvotes: 1
Views: 2639
Reputation: 106
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.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/json');
http.setRequestHeader('Accept', 'application/json, text/plain, */*');
http.onreadystatechange = function() {
if (http.readyState == 4 && http.status == 200) {
console.log('xhr:', http.getAllResponseHeaders());
}
}
http.send(params);
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?!
Upvotes: 0
Reputation: 405
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:
@Configuration
public class RestConfigs {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addExposedHeader(MyUtils.AUTHENTICATION); //Header String
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("PUT");
config.addAllowedMethod("DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
Upvotes: 0
Reputation: 2460
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 header
this.http.post('http://localhost:9090/api/user/login',
{ withCredentials: true },
this.cred)
For more springboot JWT work with Angular, checkout Springboot JWT Starter
Upvotes: 0
Reputation: 1951
The browser does not expose custom headers to the app by default.
You will need the following header in your Backend Cors config
'Access-Control-Expose-Headers' 'Authorization';
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.
Upvotes: 1