What could cause the original 'OAuth2' state parameter to be null in org.springframework.social.connect.web.ConnectSupport? - oauth-2.0

I am trying to use Spring Social on my application and I noticed while debugging that the original 'OAuth2' state parameter is always null on my app.
See Spring Social source code for org.springframework.social.connect.web.ConnectSupport below:
private void verifyStateParameter(NativeWebRequest request) {
String state = request.getParameter("state");
String originalState = extractCachedOAuth2State(request);//Always null...
if (state == null || !state.equals(originalState)) {
throw new IllegalStateException("The OAuth2 'state' parameter is missing or doesn't match.");
}
}
private String extractCachedOAuth2State(WebRequest request) {
String state = (String) sessionStrategy.getAttribute(request, OAUTH2_STATE_ATTRIBUTE);
sessionStrategy.removeAttribute(request, OAUTH2_STATE_ATTRIBUTE);
return state;
}
Can anyone please help?
edit: I do see the state parameter being passed back by facebook:
Request URL:https://www.facebook.com/v2.5/dialog/oauth?client_id=414113641982912&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fconnect%2Ffacebook&scope=public_profile&state=0b7a97b5-b8d1-4f97-9b60-e3242c9c7eb9
Request Method:GET
Status Code:302
Remote Address:179.60.192.36:443
edit 2: By the way, the exception I get is the following:
Exception while handling OAuth2 callback (The OAuth2 'state' parameter is missing or doesn't match.). Redirecting to facebook connection status page.

It turned out that the issue was caused by the fact that I was relying on headers - as opposed to cookies - to manage the session.
By commenting out the following spring session configuration bean:
#Bean
public HttpSessionStrategy sessionStrategy(){
return new HeaderHttpSessionStrategy();
}
The oauth2 state parameter issue was sorted.
P.S. Now I have got to find a way to get Spring Social to work with my current configuration of Spring Session...
Edit: I managed to keep the HeaderHttpSessionStrategy (on the spring session side) and get it to work by implementing my own SessionStrategy (on the spring social side) as follows:
public class CustomSessionStrategy implements SessionStrategy {
public void setAttribute(RequestAttributes request, String name, Object value) {
request.setAttribute(name, value, RequestAttributes.SCOPE_SESSION);
}
public Object getAttribute(RequestAttributes request, String name) {
ServletWebRequest servletWebRequest = (ServletWebRequest) request;
return servletWebRequest.getParameter(name);
}
public void removeAttribute(RequestAttributes request, String name) {
request.removeAttribute(name, RequestAttributes.SCOPE_SESSION);
}
}

Try this work around and see if that works for you:
To my surprise I opened application in a 'incognito' browser and everything worked. Just like that. I think before something got cached and was causing the issue.
I ran into this issue today, My application was working perfectly fine. I just took a break for few hours and when I ran it again it started complaining about 'The OAuth2 'state' parameter is missing or doesn't match.'
The state param is first put into the session then the request goes out to facebook and the request comes back with the same state param but when spring is looking for session object to get the state param, it is not finding the session. I think it is not finding the session because when the request comes back it thinks that it is a different client (or host), even though the old HttpSession object still exists. The container maintains a HttpSession per client.

What you're getting from Facebook is not a request attribute , it's a request parameter.
You should get it by something like:
request.getParameter("state")

Related

OWIN authentication middleware: logging off

OWIN beginner here. Please be patient...
I'm trying to build an OWIN authentication middleware which uses form posts to communicate with my external authentication provider. I've had some success with getting the authentication bits working. In other words, I can:
communicate with the remote provider through form post;
process the response returned by the remove provider
If everything is ok, I'm able to signal the default authentication provider
THis in turn gets picked up by the cookie middleware which ends up generating the authentication cookie
So far, so good. Now, what I'd like to know is how to handle a log off request. Currently, the controller will simply get the default authentication manager from the owin context and call its SingOut method. This does in fact end my current session (by removing the cookie), but it really does nothing to the existing "external" session.
So, here are my questions:
1. Is the authentication middleware also responsible for performing log off requests?
2. If that is the case, then can someone point me to some docs/examples of how it's done? I've found some links online which describe the logging in part, but haven't found anything about the log off process...
Thanks.
Luis
After some digging, I've managed to get everything working. I'll write a few tips that might help someone with similar problems in the future...
Regarding the first question, the answer is yes, you can delegate the logoff to the middleware. If you decide to do that, then your middleware handler should override the ApplyResponseGrantAsync method and check if there's a current revoke request. Here's some code that helps to illustrate the principle:
protected override async Task ApplyResponseGrantAsync() {
var revoke = Helper.LookupSignOut(Options.AuthenticationType,
Options.AuthenticationMode);
var shouldEndExternalSession = revoke != null;
if (!shouldEndExternalSession) {
return;
}
//more code here...
}
After checking if there's a revoke request, and if your external authentication provider is able to end the response through a redirect, then you can simply call the Response.Redirect method (don't forget to check for the existance of redirect - ex.: if you're using asp.net identity and MVC's automatically generated code, then the sign out will redirect you to the home page of your site).
In my scenario, things were a little more complicated because communication with my authentication provider was based of form posts (SAML2 messages with HTTP Post binding). I've started by trying to use Response.Write to inject the HTML with the autopostback form into the output buffer:
protected override async Task ApplyResponseGrantAsync() {
//previous code + setup removed
var htmlForm = BuildAndSubmitFormWithLogoutData(url,
Options.UrlInicioSessaoAutenticacaoGov);
Response.StatusCode = 200;
Response.ContentType = "text/html";
await Response.WriteAsync(htmlForm);
}
Unfortunately, it simply didn't work out. Not sure on why, but the browser insisted in redirecting the page to the URL defined by the Logoff's controller method (which was redirecting the page to its home page or '/'). I've even tried to remove the location HTTP header from within the ApplyResponseGrantAsync method, but it still ended up redirecting the user to the home page (instead of loading the predefined HTML I was writing).
I've ended up changing the redirect so that it gets handled by my middleware. Here's the final code I've ended up with in the ApplyResponseGrant method:
protected override async Task ApplyResponseGrantAsync() {
//previous code + setup removed
//setup urls for callbabk and correlation ids
var url = ...; //internal cb url that gets handled by this middleware
Response.Redirect(url);
}
This redirect forced me to change the InvokeAsync implementation so that it is now responsible for:
Checking for a new authentication session
Checking for the end of an existing authentication session (handle the logoff response from the external provider)
Checking if it should generate a new form post html message that ends the current session controlled by the external provider
Here's some pseudo code that tries to illustrate this:
public override async Task<bool> InvokeAsync() {
if (Options.InternalUrlForNewSession.HasValue &&
Options.InternalUrlForNewSession == Request.Path) {
return await HandleLoginReply(); /login response
}
if (Options.InternalUrlExternalSessionEnded.HasValue &&
Options.InternalUrlExternalSessionEnded == Request.Path) {
return await HandleLogoffReply();//logoff response
}
if (Options.InternalUrlForEndingSession.HasValue &&
Options.InternalUrlForEndingSession == Request.Path) {
return await HandleStartLogoutRequest(); //start logoff request
}
return false;
}
Yes, in the end, I've ended with an extra request, which IMO shouldn't be needed. Again, I might have missed something. If someone manages to get the ApplyResponseGrantAsync to return the auto submit post (instead of the redirect), please let me know how.
Thanks.

Why does anonymous user get redirected to expiredsessionurl by Spring Security

I'm really trying to understand how Spring Security works, but I'm a bit lost at the moment. Here's the simple scenario:
User visits the website home page but doesn't log in
SecurityContextPersistenceFilter logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter populates SecurityContextHolder with an anonymous token
A session is created with ID = C2A35ED5A41E29865FF53162B0024D52
User lets the page sit idle until the session times out
User clicks on the About page (or home page again)
SecurityContextPersistenceFilter again logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter again populates SecurityContextHolder with an anonymous token
SessionManagementFilter logs that requested session ID C2A35ED5A41E29865FF53162B0024D52 is invalid
SessionManagementFilter logs that it is starting a new session and redirecting to /invalidsession
These pages are configured to .authorizeRequests().antMatchers("/","/home","/about").permitAll(). I have the invalid session option turned on to handle authenticated users: .sessionManagement().invalidSessionUrl("/errors/invalidSession"). If I comment out that option, then everything described above is exactly the same EXCEPT for step #10 - SessionManagementFilter sees that the requested session ID is invalid (#9) but does NOT start a new session and perform the redirect (#10).
WHY? What can I do to keep the invalid session option but correctly handle anonymous users, i.e., not be redirected? Or is that just not possible and I'll have to handle authenticated users separately? I'd be very grateful if anyone can help me understand what's happening here and point me in a direction to solve this. Let me know if you need to see my full http configuration.
EDIT
I ran a series of tests with anonymous and registered (authenticated) users. If .sessionManagement().invalidSessionUrl("/errors/invalidSession") is enabled then both types of users will eventually arrive at the error page. Authenticated users with RememberMe unchecked are the same as anon users. If RememberMe is checked, then the error page appears once RememberMe times out.
If I disable the invalid session option, no users ever get the error page (which makes sense). Both types of users can browse public pages as long as they want and authenticated users will be asked to log in after the session or RememberMe expires.
If you're interested the code involved here is in SessionManagementFilter
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
If .sessionManagement().invalidSessionUrl is enabled the default method SimpleRedirectInvalidSessionStrategy is called, which executes this piece of code:
if (createNewSession) {
request.getSession();
}
redirectStrategy.sendRedirect(request, response, destinationUrl);
The createNewSession boolean can be set through setCreateNewSession(boolean createNewSession), which is described as:
Determines whether a new session should be created before redirecting (to avoid possible looping issues where the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL does not pass through the SessionManagementFilter.
So, it looks to me like .sessionManagement().invalidSessionUrl works best for sites where all pages are authenticated. The options I'm looking at are a custom filter placed before the SessionManagementFilter that checks the page access and turns 'createNewSession' on/off as needed or turning off the invalid session option and handling it elsewhere for authenticated pages (?). I also stumbled across <%# page session=“false” %> in this SO question - Why set a JSP page session = “false” directive? - which I'm going to look into further. Being so new to Spring Security I don't have a good sense of the best practice for handling this situation correctly. Any help would be appreciated.
OK, so I've spent the last couple of weeks digging around in Spring Security trying to understand how it all fits together. I'm still learning, but for this particular situation I found two approaches that work.
The obvious one is to just bypass security for public pages like this:
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/resources/**")
;
}
I still don't know enough about web security in general to know if this is an acceptable approach or not, but it allows anonymous users to browse the site w/o ever getting an invalid session error.
The harder solution (for a Java and Spring noob like me) is based upon these SO questions:
Spring security invalid session redirect
How to set a custom invalid session strategy in Spring Security
The default SimpleRedirectInvalidSessionStrategy class is final which meant I had to create basically a copy of that class (not sure how good an idea that is). You can't use a session attribute because the session has been destroyed by the time it gets to this strategy so I created a helper class for a session cookie called authUser (I can post the class if anyone wants to see it). The cookie is created or updated in the LoginSuccessHandler or RememberMeSuccessHandler and it indicates if the user is anonymous or authenticated:
authCookie.setCookie(request, response, "anonymousUser");
or
authCookie.setCookie(request, response, authentication.getName());
I'm currently using the actual login only for testing purposes - it will ultimately be just a simple yes/no indicator of some sort. CustomLogoutSuccessHandler resets it to anonymousUser
The invalid session method looks like this:
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String url = destinationUrl;
//reset context default value
redirectStrategy.setContextRelative(false);
if (authCookie.isCurrentCookieAnonymous()) {
//pass the URL originally requested by the anonymous user
url = request.getRequestURI();
//the URL needs to have the context removed
redirectStrategy.setContextRelative(true);
}
//always revert to anonymous user
authCookie.setCookie(request, response, "anonymousUser");
logger.debug("Starting new session (if required) and redirecting to '" + url + "'");
if (createNewSession)
request.getSession();
redirectStrategy.sendRedirect(request, response, url);
}
Again, I can post the full class if requested.
The SecurityConfig class includes the following:
#Bean
public SessionManagementBeanPostProcessor sessionManagementBeanPostProcessor() {
return new SessionManagementBeanPostProcessor();
}
protected static class SessionManagementBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new RedirectInvalidSession("/errors/invalidSession"));
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
My testing so far has been successful for both anonymous and authenticated users, but this approach has not been production tested.

Override error code on AuthorizationCodeProvider.Create

This question is about the implementation of the Authorization Code flow using Owin in Asp.net Wep Api.
I was trying to handle some error that might happen on my AuthorizationCode code creation. Apparently I can't redirect my self to the Client Redirect URI with he correct error code which is "server_error"
The following is my code :
private static void CreateAuthorizationCode(AuthenticationTokenCreateContext context)
{
try
{
//Some Code to create and save the AuthorizationCode that can throw an Exception
context.SetToken(code);
}
catch (Exception ex)
{
logger.Fatal(ex);
var redirectUri = GetRedirectUri();
var redirectLocation = string.Format("{0}?code={1}", redirectUri, "server_error");
context.Response.Redirect(redirectLocation);
}
}
But I get redirected by the framework to the redirect Uri with https://redirecturi?error=unsupported_response_type !
Is this a normal behavior ? Or maybe there is any other way to handle those kind of scenario and set by myself the error code !?
PS : I created an issue in Github about that : https://github.com/aspnet/Security/issues/375 no answer so far !
Thank you.
Is this a normal behavior ? Or maybe there is any other way to handle those kind of scenario that I'm missing?
Normal, I dunno. But expected, definitely: when using an IAuthenticationTokenProvider, you're not supposed to alter the HTTP response.
Why there is not way to set by myself the error using the AuthenticationTokenCreateContext object like context.SetError("my_error") ?
Unlike the ValidateAuthorizeRequest notification, it hasn't been designed to allow you to return an error.
Sadly, there's no way to return a server_error response from an IAuthenticationTokenProvider, since OAuthAuthorizationServerHandler will always use unsupported_response_type if you don't provide an authorization code: https://github.com/jchannon/katanaproject/blob/master/src/Microsoft.Owin.Security.OAuth/OAuthAuthorizationServerHandler.cs#L204
FYI, this is something we fixed recently in AspNet.Security.OpenIdConnect.Server (a fork of the OAuth2 authorization server shipped with Katana 3): https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/issues/112#issuecomment-125040925. If your custom code returns a null authorization code, a server_error response will be automatically returned to the client application.

Session is null on first request

I'm using spring-session and I really like it. However I think I'm missing something. In my application the flow goes like this:
1) User requests HomepageController and that controller tries to put an attribute in the request:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final String sessionIds = sessionStrategy.getRequestedSessionId(request);
if (sessionIds != null) {
final ExpiringSession session = sessionRepository.getSession(sessionIds);
if (session != null) {
session.setAttribute("attr", "value");
sessionRepository.save(session);
model.addAttribute("session", session);
}
}
As you can see it will try to get the sessionID from the request-cookie, and if there's a session with that ID in the repository than use it (add attribute). This is perfect, but only after the second request. Why? Because if I restart the server than the cookie is left with the old value, and then the first request will not find the session in the repository. After the response is committed though the cookie will be updated, so the second request will be correct.
And here's the question: what is wrong with my logic and how should one develop the application in order to support the first request too?
BTW, here's a sample application that demonstrates the problem:
https://github.com/paranoiabla/spring-session-issue
If you are wanting to obtain the session, you should not use requested session id. The requested session id is just that...what the browser requests. Some problems with using requested session (some of which you already outlined):
If you clear all your cookies and make a request, then no session is requested by the browser.
As you pointed out if the data store is restarted and is not persistent, then the requested session id is invalid
If the session expires, then the requested session will be invalid
Instead, you should use the session id:
final String sessionIds = request.getSession().getId();
This will use the requested session id if it is valid, otherwise it will create a new session and ensure the session is written to the response (i.e. included in the response as a cookie).
I would say your approach is wrong, your controller does to much and you should be just using the HttpSession for which Spring Session provides support. You shouldn't also be putting the session in the model imho as you should be just accessing the HttpSession. Your application shouldn't know about Spring Session.
Your controller should look like this
#Controller
public class HomepageController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(HttpSession session) {
session.setAttribute("attr", "value");
return "homepage";
}
}
if you don't want to force session creation inject the HttpServletRequest and do getSession(false) instead of injecting the HttpSession.
Everything else (storing the session after request handling etc.) will be handled transparently by Spring Session.

DotNetOpenAuth Twitter Consume StartSignIn

Trying to use the StartSignInWithTwitter method. When the method is called soon after an exception is thrown. This is using the latest version of DotNetOpenAuth. Would it have anything to do with me developing and running with locally? (VS2010) Is this how I should be doing authentication in the first place? I do see some different ways in the Samples pack that is included with the source.
{"The remote server returned an error: (401) Unauthorized."}
My code looks like below:
public void TwitAuthInit()
{
TwitterConsumer.StartSignInWithTwitter(false).Send();
}
public ActionResult TwitAuth()
{
if (TwitterConsumer.IsTwitterConsumerConfigured)
{
string screenName;
int userId;
if (TwitterConsumer.TryFinishSignInWithTwitter(out screenName, out userId))
{
FormsAuthentication.SetAuthCookie(screenName, false);
return RedirectToAction("Home", "Index");
}
}
return View();
}
To answer your question about "Is this how I should be doing authentication in the first place?":
You probably shouldn't be calling SetAuthCookie(screenName, false) with your screenName, since screen names (I believe) can be recycled. You should instead log the user in using a unique ID, either one you create in your own user database or Twitter's, and then use the screen name only as an alias that is displayed to the user (and perhaps other users if this user were to post something for public viewing). Otherwise, when Twitter recycles a username, that user will inherit all the data from the old user on your site -- not good.
Wanted to confirm that the 401 error is indeed solved by setting a non-empty callback URL on the twitter app config page.
From the Application Type block of the settings page:
To restrict your application from using callbacks, leave this field
blank.
You have to go into TwitterConsumer.cs and change the following URLs:
Request token URL https://api.twitter.com/oauth/request_token
Authorize URL https://api.twitter.com/oauth/authorize
Access token URL https://api.twitter.com/oauth/access_token
As Twitter changed their URLs. I didn't get the memo and spent way too much time debugging this.

Resources