Configuring an AccessDeniedHandler in order to process an invalid csrf token - spring-mvc

I am in reference to Spring Security documentation about configuring CSRF protection:
Instead by default Spring Security’s CSRF protection will produce an
HTTP 403 access denied. This can be customized by configuring the
AccessDeniedHandler to process InvalidCsrfTokenException differently.
see here: http://docs.spring.io/spring-security/site/docs/3.2.6.RELEASE/reference/htmlsingle/#csrf-configure
I am unsure how to configure my handler in order to deal with the invalid CSRF token.
private AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandler() {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// TODO: deal with InvalidCsrfTokenException
response.setStatus(HttpStatus.FORBIDDEN.value());
}
};
}
I use angular on the client side to communicate with my Spring app in REST.
What is the best way to deal with stale/invalid CSRF tokens?
Should I use the the AccessDeniedHandler in order to add a custom http response header indicating that the CSRF token is invalid and process that on the client side? But how can I request a fresh CSRF token from JS?
Is there another and better way to proceed and how can I
process the InvalidCsrfTokenException differently
?

If you provide a detailed error message then use the AccessDeniedHandler. The handler manage the InvalidCsrfTokenException and MissingCsrfTokenException
And why do you want to generate the csrf token? Everytime you request a site spring will generate it for you. But if you really want to implement an own csrf strategie take a look at the CsrfAuthenticationStrategy
Spring call this class everytime in the SessionManagementFilter

Related

How to enable CookieCsrfTokenRepository.withHttpOnlyFalse() and #EnableJdbcHttpSession?

I have a Spring boot application with Spring Authorization Server (the new 4.0.1 package Spring Authorization)
The server is in a cluster of servers thus it needs to save the session in a DB so I use #EnableJdbcHttpSession.
The Authorization service has state change request (e.g login etc...) with HTTP POST called from client written in Angular JS.
In order to secure my HTTP POST requests I use csrf e.g. csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
Its seems that #EnableJdbcHttpSession invoke the HttpSessionCsrfTokenRepository so I can't define the post request to work with CookieCsrfTokenRepository.
When I define CookieCsrfTokenRepository the SESSION cookie is not getting created, is it possible to define both somehow ?
#Configuration
#EnableJdbcHttpSession
public class SecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize ->
authorize.anyRequest().authenticated()
).formLogin(withDefaults())
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
return http.build();
}
}
This code will fix the issue
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))

How do I get log output from JwtSecurityTokenHandler?

I have an ASP.NET Core 2.1 Web Application project that uses JWT tokens for authenticating the Web API that's built-in to the project. It works fine when I run it locally on my machine, but when I deploy it to Azure (with identical environment and app-settings) it simply returns empty HTTP 401 responses to requests from my authenticated clients and I need to find out why so I can fix it.
I enabled logging of every detail in ASP.NET Core, however I never received any useful output.
First, I added Serilog.AspNetCore and the Console sink to the project through NuGet, then configured logging at Verbose level in Program.cs:
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Verbose)
.MinimumLevel.Override("System", LogEventLevel.Verbose)
.MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Verbose)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate)
.CreateLogger();
CreateWebHostBuilder( args ).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(String[] args)
{
return WebHost.CreateDefaultBuilder( args )
.ConfigureLogging( (ctx, cfg ) =>
{
cfg.ClearProviders();
} )
.UseStartup<Startup>()
.UseSerilog();
}
}
But when I run my web-application on Azure (with console stdout logging to file) I got this output:
[04:13:10 Verbose] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Authorization Filter: Before executing OnAuthorizationAsync on filter
Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter.
[04:13:10 Verbose]
IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler
HandleAuthenticateAsync called
[04:13:10 Debug]
IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler
AuthenticationScheme: Bearer was not authenticated.
[04:13:10 Information]
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService
Authorization failed.
[04:13:10 Verbose] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Authorization Filter: After executing OnAuthorizationAsync on filter
Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter.
[04:13:10 Information]
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
[04:13:10 Verbose] Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker
Before executing action result Microsoft.AspNetCore.Mvc.ChallengeResult.
[04:13:10 Information] Microsoft.AspNetCore.Mvc.ChallengeResult
Executing ChallengeResult with authentication schemes (["Bearer"]).
[04:13:10 Verbose]
IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler
Forwarding challenge to scheme: BearerIdentityServerAuthenticationJwt
Note how despite verbose logging, the error messages (repeated below) don't give me any explanation:
AuthenticationScheme: Bearer was not authenticated.
Authorization failed.
I dug around the ASP.NET Core Security source-code to see that JwtBearerHandler.HandleAuthenticateAsync doesn't do much logging of its own, but it does call into the not-open-sourced System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler which does normally do a lot of logging, including detailed reasons (e.g. (with IDX10209-type error codes in strings), but I don't know why it isn't outputting anything I can capture.
How do I log messages from JwtSecurityTokenHandler?
I found the problem:
My HttpClient (that was sending the HTTP Authorization header Bearer token) was unintentionally sending it to a http:// URI that immediately received a 301 redirect to a https:// URI. The redirect was performed by IIS without the ASP.NET Core pipeline getting involved.
The HttpClient class does not re-send the Authorization header following a redirect (this is by-design).
I never noticed this because my HttpClient's received HttpResponseMessage had a reference to the original request which had the Authorization header, not the post-redirect request that lacked the header. I had to use Fiddler with the HTTPS proxy to see the second request was lacking the Authorization header.
When IdentityServerAuthenticationHandler or ASP.NET Core's own JwtBearerHandler receives a request with no Authorization header it does not call into JwtSecurityTokenHandler at all. To see this, open the JwtBearerHandler.cs file in the ASP.NET Core Security Git repo and look at HandleAuthenticateAsync: It has this logic:
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers["Authorization"];
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
So in my case, it never actually called JwtSecurityTokenHandler at all, hence the lack of output messages about JWT validation.
However the output messages I did receive did not help. They're both misleading:
"AuthenticationScheme: Bearer was not authenticated." should have been something like "AuthenticationScheme: No Bearer token was present in the request." instead.
And "Authorization failed." should have been "Authorization skipped because no token was present in the request."
So in the end, the fix was to change the original request URI's scheme from http:// to https://.

How to use ASP.NET Core Antiforgery with AngularJS app on separate subdomain?

I have an ASP.NET Core backend with some Web APIs, and an AngularJS client on separate subdomains (Actually on localhost with different ports), and I can't seem to get Antiforgery to work.
I get 400 Bad request response on APIs protected with [ValidateAntiForgeryToken], this is my backend code :
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
...
app.Use(next => context => {
tokens = antiforgery.GetAndStoreTokens(context);
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false });
return next(context);
});
First problem i've got is that the cookie is not set on my AngularJS app, i've resolved it by setting WithCredentials to true : $httpProvider.defaults.withCredentials = true;
The second one is that the X-XSRF-TOKEN header is not set automatically by AngularJS, and i've solved it by setting an interceptor : config.headers['X-XSRF-TOKEN'] = $cookies.get('XSRF-TOKEN');
Now I can receive the token and send it with my $http requests, I don't understand why API calls are rejected ! Is it related that the applications are not on the same port ?
Thanks in advance :)
Try using this
config.headers['XSRF-TOKEN'] = $cookies.get('XSRF-TOKEN');
Use [AutoValidateAntiforgeryToken] instead of [ValidateAntiForgeryToken]
ValidateAntiForgeryToken: This filter validates the request token on each and every method it is placed on, regardless of the HTTP verb. So it even validates GET and HEAD requests where there is no antifrogery token to validate so causes 400 bad request.
AutoValidateAntiforgeryTokenAttribute: AutoValidateAntiforgeryToken is almost similar to ValidateAntiforgeryToken except for the fact that it doesn’t validate tokens on GET, HEAD, OPTIONS, and TRACE requests.
Generally, a controller may contain GET as well as POST action methods. POST action methods require validating the anti-forgery token and not the GET action methods. So, if the ValidateAntiforgeryToken is declared on the controller, the HTTP GET requests become invalid, it would throw an error.
AutoValidateAntiforgeryToken can be used in such case.
Sometimes it is not required that you validate all the tokens, for example, with requests like:
GET
HEAD
OPTIONS
TRACE
AutoValidateAntiforgeryToken should be used in such cases.
It is advisable to use AutoValidateAntiforgeryTokenAttribute rather than using ValidateAntiForgeryTokenAttribute globally because if we apply ValidateAntiForgeryTokenAttribute globally then we will get error in case of requests like GET, HEAD, TRACE, etc. becuase in such request server does not receive the anti-forgery tokens which will cause validation errors for those requests.
Sometimes there might be some requirements for ignoring the anti-forgery tokens or you need to ignore the tokens for specific actions of the controllers. In such cases, you can use an IgnoreAntiforgeryToken filter.

Why isn't OAuth2 client refreshing expired access_token?

I've got a client app configured with #EnableOAuth2Sso and #EnableZuulProxy, and a resource server (separate app) configured with #EnableOAuth2Resource. I can see that the client correctly authenticates to the resource server with Authorization: Bearer {access_token here}, but when once the access token expires, the proxied resource server request fails permanently.
[Edited]
I've modified my resource server by providing a custom RemoteTokenServices bean that uses OpenAM's /tokeninfo endpoint to decide whether an access_token remains valid. (The Spring-provided RemoteTokenServices bean attempts to POST, which gets a 405 from OpenAM). When I detect the access_token is invalid, I throw InvalidTokenException from my.spring.oauth2.OpenAMRemoteTokenServices#loadAuthentication. Now, my resource server is (I think correctly) sending HTTP 401 on the response to the client, in the case where the access_token has expired.
Still, the client is not attempting to refresh the token.
Maybe my mental model is wrong. I expect the client, in the case of expired access_token, to automatically use the refresh_token to obtain a new one. I don't know whether I think it should proactively refresh an access_token (within some epsilon before expiry time), or wait for a downstream request to fail and try refreshing then. But my client appears to be doing neither, and I can't tell why not.
As stated in this git issue: https://github.com/spring-guides/tut-spring-security-and-angular-js/issues/140, the problem might be related to the fact that with versions 1.4 and above of spring boot the Zuul filter that handles the downstream of access tokens to services (org.springframework.cloud.security.oauth2.proxy.OAuth2TokenRelayFilter) is missing a bean of type OAuth2RestTemplate, which is used by the filter itself to automatically handle the refresh_token grant when access tokens expire.
I had the same issue and I solved it by adding in a configuration class the following bean:
#Configuration
public class ZuulConfiguration {
#Bean
protected OAuth2RestTemplate oauth2RestTemplate(OAuth2ProtectedResourceDetails resource,
OAuth2ClientContext context) {
return new OAuth2RestTemplate(resource, context);
}
}

Propagating AccessDeniedException in Spring Security

In my web application I am using Spring Security and Spring MVC.
I have secured a couple of methods with #Secured annotation and configured Spring Security in such a way that when one of those methods is accessed without the proper role, the user is taken to the login page. However, I do not want that behaviour when the offending request comes from Ajax, so I implemented the custom #ExceptionHandler annotated method to determine the request's context.
This is my exception handler:
#ExceptionHandler(AccessDeniedException.class)
public void handleAccessDeniedException(AccessDeniedException ex, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (isAjax(request)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
} else {
throw ex;
}
}
This way I can both handle the exception myself (for example, log an attempt of accessing the #Secured method) and then let Spring do its part and redirect the user to the login page by rethrowing the AccessDeniedException. Also, when the request comes from Ajax I set the response status to SC_UNAUTHORIZED and handle the error on the client side.
Now, this seems to be working fine, but I am getting the following ERROR each time I rethrow the exception from the handleAccessDeniedException method:
ERROR org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver - Failed to invoke #ExceptionHandler method: public void app.controller.BaseController.handleAccessDeniedException(org.springframework.security.access.AccessDeniedException,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.lang.Exception
org.springframework.security.access.AccessDeniedException:
at app.controller.BaseController.handleAccessDeniedException(BaseController.java:23)
at app.controller.BaseController$$FastClassByCGLIB$$8f052058.invoke(<generated>)
at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
(...)
I have not added any exception handling specifics to spring xml configuration files.
I do not see any issues with the app itself, but the error is there and since I am quite new to Spring MVC and Spring Security, I am guessing that I am not doing this properly. Any suggestions? Thanks!
Your exception handler isn't supposed to throw another exception. It's supposed to deal with it and send a response. It's a good idea to check the code if you get an error from a class to see how it behaves.
For the non-ajax case, you'd be better to redirect the response to the login page, if that's what you want. Alternatively, you can customize the AuthenticationEntryPoint used by Spring Security instead and omit AccessDeniedExceptions from MVC handling. The behaviour would essentially be the same as the defaul LoginUrlAuthenticationEntryPoint but you would extend it to return a 403 when an ajax request is detected.

Resources