I would like to post using Axios to my Spring Boot server. If I disable csrf using .csrf().disable() it works correctly however it fails when enabled.
I've tried adding X-CSRF-TOKEN to the header, or _csrf in the body but it is saying it is invalid. Checking the request the csrf is being passed in as expected.
CSRF Controller
#RequestMapping("/csrf")
public String csrf(final CsrfToken token) {
return token.getToken();
}
Spring Security
httpSecurity
.antMatcher("/api/**")
.cors().and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
Axios
axios
.post(
`/api/doSomething`,
{ id: id, _csrf: csrf},
{
withCredentials: true,
headers: {
'X-CSRF-TOKEN': csrf
}
}
)
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
MetaTags react-meta-tags store the CSRF
<MetaTags>
<meta name="csrf-token" content={csrfToken} />
</MetaTags>
Axios to get the CSRF token
axios
.get('/csrf', {
withCredentials: true
})
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
Function to get the CSRF token from the meta tags
function getCSRFFromPage(): string | null {
const element = document.querySelector("meta[name='csrf-token']");
if (element !== null) {
const csrf: string | null = element.getAttribute('content');
return csrf;
} else {
return null;
}
}
Error
Invalid CSRF token found for <url>
What could be causing this to fail?
There are two possible causes.
First of all, the CSRF token endpoint should match the Spring Security configuration. In your example, you're using antMatcher("/api/**"), but CSRF token endpoint is /csrf. This should likely become /api/csrf.
The second part is that the CSRF token changes after each request. You didn't show the code where you invoke getCSRFFromPage(), but this should happen before each call you make. If not, then only the first call with that CSRF token will succeed. All consecutive calls will fail.
If you don't like making an additional call with each request, then you can use the CookieCsrfTokenRepository:
httpSecurity
// ...
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
By doing so, each time you make a request, you'll get an XSRF-TOKEN cookie containing the next CSRF token. This should be passed within the X-CSRF-TOKEN or X-XSRF-TOKEN header. Many libraries, including Axios do this out of the box, so you don't have to do anything else.
You can customize this behavior by configuring the xsrfHeaderName or xsrfCookieName properties (see Request Config).
Related
I am implementing login functionality for my project. In frontend I am using Angular 8.
I have implemented in such way so Angular 8 and Springboot is running on same port 8090.
I have routing as
const routes: Routes = [
{ path: '', component: EmployeeComponent,canActivate:[AuthGaurdService] },
{ path: 'addemployee', component: AddEmployeeComponent,canActivate:[AuthGaurdService]},
{ path: 'login', component: LoginComponent },
{ path: 'logout', component: LogoutComponent,canActivate:[AuthGaurdService] },
];
Java side : I have already set it to permit all /login request
WebSecurityConfig
#Override
protected void configure(HttpSecurity httpSecurity)
throws Exception
{
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/login").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll().anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
but still while calling for localhost:8090/login, i am facing on browser
Whitelabel Error Page This application has no explicit mapping for
/error, so you are seeing this as a fallback.
Wed Feb 26 14:42:50 IST 2020 There was an unexpected error (type=Not
Found, status=404). No message available
In backend, I am facing
2020-02-26 14:30:44.045 WARN 5184 --- [nio-8090-exec-1]
org.freelancing.utils.JwtRequestFilter : JWT Token does not begin
with Bearer String 2020-02-26 14:42:49.945 WARN 5184 ---
[nio-8090-exec-3] org.freelancing.utils.JwtRequestFilter : JWT Token
does not begin with Bearer String 2020-02-26 14:42:51.287 WARN 5184
--- [nio-8090-exec-4] org.freelancing.utils.JwtRequestFilter : JWT Token does not begin with Bearer String
I think it is going in this block
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain)
throws ServletException,
IOException
{
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer "))
{
jwtToken = requestTokenHeader.substring(7);
try
{
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
}
catch (IllegalArgumentException e)
{
System.out.println("Unable to get JWT Token");
}
catch (ExpiredJwtException e)
{
System.out.println("JWT Token has expired");
}
}
else
{
logger.warn("JWT Token does not begin with Bearer String");
}
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
{
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails))
{
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext()
.setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
what I need is to render the login page and then take the credential and create the header.But even on hitting localhost:8090/login it is asking for header in above code as the header is null that's i am getting error:
JWT Token does not begin with Bearer String
LoginComponent
<div class="container">
<div>
User Name : <input type="text" name="username" [(ngModel)]="username">
Password : <input type="password" name="password" [(ngModel)]="password">
</div>
<button (click)=checkLogin() class="btn btn-success">
Login
</button>
</div>
new to security stuff, Please help
I assume your angular application won't show at all when calling it. You should not try to run two different services on the same port on the same machine, since your browser will not be able to differ what service should get your requests.
Currently you are sending a GET-Request to your API (URL:*/login) which you may not have set. Therefore you'll get the 404 in the error message, but you expect the request to be directed to your angular app to show your application (e.g. login mask).
I've currently set up a Spring Cloud Gateway Reverse Proxy with the intention of:
a) Handling Authentication of multiple OAuth/OIDC providers, including obtaining a Token
b) Look up the details from the provider locally, ensuring that the OAuth user x Oauth provider combination are authorised
c) If authorised look up the Grants/Permissions, and forward the request to SCG, with a JWT containing details of the authorised principal.
d) If not authorised display a page displaying pertinent details from the OAuth2 Auth, and explain that they are not authorised.
I have achieved most steps, but I am having trouble incorporating step c) into Spring Security Webflux
What I want to do is take the OAuth2AuthenticationToken obtained from the Authentication exchange, perform the lookup in step, and return a
bespoke Prinicipal based on results.
This would then be used via code to either trigger the SCG behaviour, or display the page.
spring-boot.version>2.1.6.RELEASE
spring-cloud.version>Greenwich.SR2
My problem is I don't know the best way of doing this.
Use some hook in the OAuth2 client to perform the extra auth steps. I may need to return an OAuth2Principal in this case
Add an extra security filter into the chain after the authentication.
This would replace the OAuth2Principal with my own prncipal. I'm not sure whether it is legal to replace a Principal after it has been authenticated, possibly removing the authentication status
Writing a custom AuthN Provider that would proxy to the OAuth client, and once competed run it's own logic before signalling that it is authenticated. This seems a complicated approach, and I'm not sure what classes I would use for this.
I've read the Spring Security documentation, and understand the general architecture of Spring Security, but cannot work out the best way of solving this.
This is my spring security filter logic
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
#Profile("oauth")
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return addAuthZ(http)
.oauth2Login()
.and().build();
}
private ServerHttpSecurity addAuthZ(ServerHttpSecurity http) {
return http.authorizeExchange()
.anyExchange().authenticated().and();
}
}
And here is the config, I am using sample OAuth2 providers Google and Facebook and a custom OAuth2 provider provided using CAS
spring:
security:
oauth2:
client:
registration:
google:
client-id: SET_ME
client-secret: SET_ME
facebook:
client-id: SET_ME
client-secret: SET_ME
sgd-authn:
provider: sgd-authn
client-id: SET_ME
client-secret: SET_ME
scope: openid
client-authentication-method: secret
authorization-grant-type: authorization_code
#redirect-uri: "{baseUrl}/oauth2/
redirect-uri-template: "{baseUrl}/{action}/oauth2/code/{registrationId}"
provider:
# These are needed for talking to CAS OIDC
sgd-authn:
authorization-uri: ${cas.url}/oidc/authorize
token-uri: ${cas.url}/oidc/accessToken
jwk-set-uri: ${cas.url}/oidc/jwks
user-info-uri: ${cas.url}/oidc/profile
user-name-attribute: sub
Well I managed to get something working using option 2, adding a filter after authentication.
Write a filter that takes in an Authentication (OAuth), runs it through some logic, and returns a new Authentication object that is instanceof a specific superclass
Arrange for this to return isAuthenticated = false if we could not look up the OAuth2 details.
Write a bespoke handler for NotAuthenticated
The filter is registered using something like this:
#Bean
public SecurityWebFilterChain springSecurityFilterChain(
ServerHttpSecurity http,
WebFilter proxyAuthFilter
) {
return http.addFilterAt(proxyAuthFilter, SecurityWebFiltersOrder.AUTHENTICATION); // Do configuration ...
}
and the filter is something like this:
public class ProxyAuthFilter implements WebFilter {
static private Logger LOGGER = LoggerFactory.getLogger(ProxyAuthFilter.class);
private final AuthZClientReactive authZClient;
public ProxyAuthFilter(AuthZClientReactive authZClient) {
this.authZClient = authZClient;
}
/**
* Process the Web request and (optionally) delegate to the next
* {#code WebFilter} through the given {#link WebFilterChain}.
*
* #param exchange the current server exchange
* #param chain provides a way to delegate to the next filter
* #return {#code Mono<Void>} to indicate when request processing is complete
*/
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return ReactiveSecurityContextHolder.getContext()
.flatMap(this::transform)
.then(chain.filter(exchange));
}
private Mono<Authentication> transform(SecurityContext securityContext) {
Authentication authentication = securityContext.getAuthentication();
if (authentication.isAuthenticated()) {
return authenticate(authentication)
.map(a -> {
securityContext.setAuthentication(a);
return a;
});
} else {
LOGGER.info("ProxyFilter - not authenticated {}", authentication);
return Mono.just(authentication);
}
}
// Runs the chain, then returns a Mono with the exchange object which completes
// when the auth header is added to the request.
private Mono<Authentication> authenticate(Authentication authentication) {
LOGGER.info("ProxyFilter - Checking authorisation for {}", authentication);
Mono<AuthTokenInfo.Builder> authInfo = null;
// Catch if this filter runs twice
if (authentication instanceof ProxyAuthentication) {
LOGGER.info("ProxyAuthentication already found");
return Mono.just(authentication);
} else if (authentication instanceof UsernamePasswordAuthenticationToken) {
// If lookup is successful, returns instance of ProxyAuthentication
return getUsernamePasswordAuth((UsernamePasswordAuthenticationToken) authentication);
} else if (authentication instanceof OAuth2AuthenticationToken) {
// If lookup is successful, returns instance of ProxyAuthentication
return getOAuthAuth((OAuth2AuthenticationToken) authentication);
} else {
LOGGER.info("Unknown principal {}", authentication);
// Signals a failed authentication, can be picked up by error page
// to display bespoke information
return Mono.just(new ProxyAuthenticationNotAuthenticated(
authentication, ProxyAuthenticationNotAuthenticated.Reason.UnknownAuthenticationType)
);
}
}
I am using JWT and Spring security for developing a Forum Application. I am getting 403 error when accessing users' endpoints. It happened after the merge, previously everything working properly. The endpoint works properly from POSTMAN but the issue occurs when accessing from browser
Nothing in the code has been mixed up, now the Authorization header is not added to the request, but only in the endpoints for users, in other cases, it works. The bare token is stored at the local storage of the browser. What could be the reason for something like that?
Angular interceptor adding authorization header:
intercept(request: HttpRequest<any>, next: HttpHandler) {
const authHeader = AUTHORIZATION_HEADER;
const accessToken = this.authService.getAuthorization();
if (accessToken !== null) {
request = request.clone({
headers: request.headers.set(authHeader, accessToken),
withCredentials: false
});
}
return next.handle(request);
}
}
Angular Auth Service
login(userCredentials: UserCredentials): Observable<any> {
return this.http
.post<AccountInfo>(`${API_URL}/login`, userCredentials, { observe: 'response' })
.pipe(
tap((response: HttpResponse<AccountInfo>) => {
const token = response.headers.get(AUTHORIZATION_HEADER);
this.storeAuthorization(token);
const body = response.body;
this.storeAccountInfo(body);
})
);
}
getAuthorization(): string {
return localStorage.getItem(AUTHORIZATION_KEY);
}
private storeAuthorization(authToken: string) {
localStorage.setItem(AUTHORIZATION_KEY, authToken);
}
private storeAccountInfo(accountInfo: AccountInfo) {
localStorage.setItem(USERNAME_KEY, accountInfo.username);
localStorage.setItem(ROLE_KEY, accountInfo.role.toString());
}
Here is the git repo containing the source code
https://github.com/PatrykKleczkowski/Forum/tree/feature/improvments
We are a team working on azure web services. What we want to achieve is having a JavaScript frontend which can communicate with our Java API App hosted in Azure.
We are using Active Directory for Authentication. And we have configured CORS within the Azure portal.
We have created a Java backend with Swagger Editor as it is described in this article. We have just advanced this example to support our own data model. So also the ApiOriginFilter class is still unchanged:
#javax.annotation.Generated(value = "class io.swagger.codegen.languages.JavaJerseyServerCodegen", date = "2016-11-08T15:40:34.550Z")
public class ApiOriginFilter implements javax.servlet.Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
chain.doFilter(request, response);
}
public void destroy() {}
public void init(FilterConfig filterConfig) throws ServletException {}
}
Our frontend runs on a local PC. So furthermore, we added our origin "http://localhost:8888" in the CORS in the azure portal.
The frontend JavaScript code looks like this:
var app = angular.module('demo', ['AdalAngular', 'ngRoute'])
.controller('GetDataController', ['$scope', '$http', '$timeout', 'adalAuthenticationService', function($scope, $http, $timeout, adalAuthenticationService) {
$scope.loggedIn = "Logged out";
$scope.responseData = "No Data";
$scope.loading = "";
$scope.loginAdal = function(){
adalAuthenticationService.login();
}
$scope.getData = function(){
$http.defaults.useXDomain = true;
delete $http.defaults.headers.common['X-Requested-With'];
$scope.loading = "loading...";
var erg = $http.get('http://<our-backend>.azurewebsites.net/api/contacts')
.success(function (data, status, headers, config) {
console.log("SUCCESS");
$scope.loading = "Succeeded";
$scope.loggedIn = "LOGGED IN";
$scope.responseData = data;
})
.error(function (data, status, header, config) {
console.log("ERROR");
$scope.loading = "Error";
console.log("data: ", data);
});
}
}]);
app.config(['$locationProvider', 'adalAuthenticationServiceProvider', '$httpProvider', '$routeProvider', function($locationProvider, adalAuthenticationServiceProvider, $httpProvider, $routeProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false,
hashPrefix: '!'
});
var theEndpoints = {
"https://<our-backend>.azurewebsites.net/api/contacts": "f0f91e4a-ad53-4a4a-ac91-431dd152f386",
};
adalAuthenticationServiceProvider.init(
{
anonymousEndpoints: [],
instance: 'https://login.microsoftonline.com/',
tenant: "<our-tenant>.onmicrosoft.com",
clientId: "f6a7ea99-f13b-4673-90b8-ef0c5de9822f",
endpoints: theEndpoints
},
$httpProvider
);
}]);
But calling the backend from the frontend we get the following error after logging in into our tenant:
XMLHttpRequest cannot load https://[our-backend].azurewebsites.net/api/contacts. Redirect from 'https://[our-backend].azurewebsites.net/api/contacts' to
'https://login.windows.net/12141bed-36f0-4fc6-b70c-
43483f616eb7/oauth2/autho…
%2Fapi%2Fcontacts%23&nonce=7658b7c8155b4b278f2f1447d4b77e47_20161115124144'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin 'null' is
therefore not allowed access.
In Chrome's developer console we see four requests to our contacts api. But all have the status code "302 Redirect". The first two entries contain the header "Access-Control-Allow-Origin:http://localhost:8888" but the other two entries do not contain this header.
EDIT: One of the first two entries is an XmlHttpRequest and one of the second entries is the same XmlHttpRequest but with https instead of http.
Based on this, we created a new filter in our backend to set the access-control-allow-origin field:
#Provider
public class CrossOriginFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException {
containerResponseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
containerResponseContext.getHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
containerResponseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
containerResponseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
containerResponseContext.getHeaders().add("Access-Control-Max-Age", "1209600");
}
}
and deleted these three fields from ApiOriginFilter:
res.addHeader("Access-Control-Allow-Origin", "*");
res.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
res.addHeader("Access-Control-Allow-Headers", "Content-Type");
Now if we run the backend locally, we see all these headers from the 2nd filter in Chrome's developer console.
But as soon as we deploy the backend to azure we loose this headers somehow and again have the same error when accessing the api from the frontend:
XMLHttpRequest cannot load https://[our-backend].azurewebsites.net/api/contacts. Redirect from 'https://[our-backend].azurewebsites.net/api/contacts' to
'https://login.windows.net/12141bed-36f0-4fc6-b70c-
43483f616eb7/oauth2/autho…
%2Fapi%2Fcontacts%23&nonce=7658b7c8155b4b278f2f1447d4b77e47_20161115124144'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
header is present on the requested resource. Origin 'null' is
therefore not allowed access.
EDIT: As I wrote there are two XmlHttpRequests made. And the second one is https. So If in the line
var erg = $http.get('http://<our.backend>.azurewebsites.net/api/contacts')
I change http to https we run into the error() callback function. And in the console we have the output:
data: User login is required
But as I wrote before. We are already logged in to our tenant. So what is going wrong?
var erg = $http.get('http://.azurewebsites.net/api/contacts')
Based on the code, you were request the service with HTTP protocol. I am able to reproduce this issue when I request the web API which protected by Azure AD with the HTTP. It seems that the Azure AD will redirect the HTTP to HTTPS.
After I change the service URL with HTTPS, the issue was fixed. Please let me know whether it is helpful.
I am trying to implent CSRF-protection with Spring Security (4.1.3) and Angular 2.0.1
There are many sources to related topics but I cannot find a clear instruction. Some of the statements even contradict each other.
I read about springs way of doing it (though the guide describes the Angular 1 way) Spring Security Guide with Angular
IT implies, that with
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
everything should work "out of the box".
Even further the angular guide to security describes the CSRF-protection as build in.
In my enviroment the POST looks like this:
There is an OPTIONS-call which returns POST, 200 OK and a XSRF-TOKEN - cookie.
my http.post adds an authorization-header and adds the RequestOption "withCredentials"
It sends three cookies, two JSessionID's and an XSRF-TOKEN that is different from the one recieved by the OPTIONS-call, no XSRF-header.
Debugging into the Spring CsrfFilter shows me that it looks for a header named X-XSRF-TOKEN and compares it to the token in the cookie named XSRF-TOKEN.
Why doesn't Angular send the header, too?
How is this secure if Spring only checks the provided cookie and the provided header with no serverside action whatsoever?
There are some similar questions like this but the only answer with 0 upvotes seems (to me) plain wrong, as CSRF, from my understanding, has to have a serverside check for the cookie validation.
This question only provides information on how to change the cookie or header name as explained here
What am I missing here? I doubt there is a mistake in the Spring Security implementation but I cannot quite get it to work.
Any ideas?
POST-call
login(account: Account): Promise<Account> {
let headers = new Headers({ 'Content-Type': 'application/json' });
headers.append('X-TENANT-ID', '1');
headers.append('Authorization', 'Basic ' + btoa(account.userName + ':' + account.password));
let options = new RequestOptions({ headers: headers, withCredentials:true });
return this.http.post(this.loginUrl, account, options).toPromise()
.then(this.extractData)
.catch(this.handleError)
}
Spring Security Config
[] csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
The problem was the application path. Spring has the option to set the cookie-path in its pipeline but it is not released yet.
I had to write my own implementation for the CsrfTokenRepository which would accept a different cookie path.
Those are the relevant bits:
public final class CookieCsrfTokenRepository implements CsrfTokenRepository
private String cookiePath;
#Override
public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
String tokenValue = token == null ? "" : token.getToken();
Cookie cookie = new Cookie(this.cookieName, tokenValue);
cookie.setSecure(request.isSecure());
// cookie.setPath(getCookiePath(request));
if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
cookie.setPath(this.cookiePath);
} else {
cookie.setPath(getRequestContext(request));
}
if (token == null) {
cookie.setMaxAge(0);
} else {
cookie.setMaxAge(-1);
}
if (cookieHttpOnly && setHttpOnlyMethod != null) {
ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
}
response.addCookie(cookie);
}
public void setCookiePath(String path) {
this.cookiePath = path;
}
public String getCookiePath() {
return this.cookiePath;
}