Skip to content

oauth2 logout doesnt really log out, JSESSION are still be retained #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
nam-pt-do opened this issue Aug 31, 2016 · 27 comments
Closed

oauth2 logout doesnt really log out, JSESSION are still be retained #121

nam-pt-do opened this issue Aug 31, 2016 · 27 comments

Comments

@nam-pt-do
Copy link

Hi,

The sample code for oauth2 doesnt seem to do log out correctly.

code:
tut-spring-security-and-angular-js/oauth2/
auth
ui
resource

when you press login again after log out, there's no prompt for a login screen.
Rather it just goes straight to "approve" dialog.

I looked into the browser cookie and there seems to be 2 JSESSION
One comes from localhost:8080/ from the ui gateway
Second comes from localhost:9999/uaa from the authserver (see fig1 below)

If i manually clear the JSESSION from localhost:9999/uaa then it correctly shows the login form again on re-click of login after logout.

Is there an option to tell authserver to not retain JSESSION?
Or can the springsecurity /logout POST ensure that authserver properly clear JSESSION?

Thanks

Nam

fig1: This cookie should be cleared at logout but is not.
image

@nam-pt-do
Copy link
Author

i've tried a number of things to the sample code which doesn't seem to work for logout.

TRIED: Javascript side clearing /uaa JSESSIONID cookie,
RESULT: failed miserably since it's "HttpOnly" cookie and javascript cannot read/modify the cookie

TRIED: Modified the auth server to session policy = NEVER
RESULT: no effect, auth still issues /uaa session JSESSIONID and logout is bypassed

TRIED: Modified the auth server to sessoin policy = STATELESS
RESULT: login breaks on redirect to /uaa

Just currious is this the state of security for oauth?
To logout you must close the browser because of HttpOnly security feature?

Thanks,

@dsyer
Copy link
Collaborator

dsyer commented Sep 2, 2016

Single logout is subtle, and you have to be careful what you wish for. For instance, if the auth server was Google and the app was your code, I don't think your users would thank you if you logged them out of Google every time they logged out of the app. So the user experience isn't always best served by trying to log out of the auth server at the same time as the app. Indeed, as you have found, it isn't easy to even do that. If you want to do it, your JavaScript client should probably just POST to /uaa/logout, after you configure the CORS protection for that endpoint. However, as soon as you have more than one app authenticated this way, you have the additional headache of trying to decide if you need to try and log out of all of them, or just the one that the user clicked on (it's highly dependent on our own product design). And that's even harder to manage and configure the security rules for.

@nam-pt-do
Copy link
Author

Thanks for the reply.

Seems like this is the nature of a distributed oauth system that we are trying to setup.

Yes we are moving towards a more micro-service/micro-app pattern and seems like we need to lose some of the "monolith" behavior people expect... is this some type of CAPs theory for security? You cant have distributed security for inter-op and have it behave like a monolith at the same time?

For now we are asking users to close their browser to end all sessions.

Is asking the user to fully close the browser the current industry standard with regards to security?
Or do people just remove the logout button entirely... implicitly implying close the browser is the new logout?

Thanks,

@dsyer
Copy link
Collaborator

dsyer commented Sep 3, 2016

Closing the browser won't end any sessions, and the cookies are generally persisted locally.
If you can define the behaviour people expect fairly precisely I expect you can implement it. It's just not a one-size-fits all thing, so there is no "industry standard" really. Removing the logout button is probably not what most apps do.

Simulating a monolith is an interesting enough idea on it's own that it might deserve a new chapter in the tutorial though. You could play tricks with iframes, and polling to check for a remote single session (chewing up bandwidth). Or, if all your apps are in the same domain, you could use a global cookie. Both ideas probably complicate the security configuration (CORS in particular), but the global cookie seems the cleanest.

@nam-pt-do
Copy link
Author

i am trying to configuring CORS on the oauth2-authserver project and modify hello.js to make a cross-site /uaa/logout before posting /logout to oauth2-ui.

I was hoping for an easy config in spring boot

  security.cors... = "mydomain.*" ;
# or 
  server.cors  = "mydomain.*";

...but there seems to be no such configs.. at least that the STS UI is showing.

So I am following some sample code for setting up a SimpleCorsFitler.java (see code below) to allow all cross-site requests.
But it's not working... I am a bit new to Spring Boot/Spring Security/Oauth2, is there an easier way to configure CORS for oauth2-authserver?

I just added this cors filter java next to the AuthServerApplication.java ...
do i need to add a config to activate the filter?

Thanks,

package demo;

import org.springframework.core.annotation.Order;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {

    public SimpleCorsFilter() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }

}
`

@nam-pt-do
Copy link
Author

further debugging, the cors filter seems to be running,

it is hitting the code, when the cross site
AJAX cross-site /uaa/logout post.

    if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            //  this hits when, the AJAX cross-site request comes
            //  ||
            //  VV
            response.setStatus(HttpServletResponse.SC_OK); 
        } else {
            chain.doFilter(req, res);
        }

but i still see on the hello.js side

XMLHttpRequest cannot load http://localhost:9999/uaa/logout. Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

below is my hello.js modifications to logout

self.logout = function() {
        console.log("post logout to uaa...");

        $http.post('http://localhost:9999/uaa/logout', {}).finally(function() {
            console.log("post log out to ui...");

            $http.post('logout', {}).finally(function() {
                console.log("redirect to root...");

                $rootScope.authenticated = false;
                $location.path("/");
            });
        })
    }

@nam-pt-do
Copy link
Author

I think my CORS fitler java modification to oauth2-authserver project
and the hello.js modification to post the cross-site /uaa/logout is working on oauth2-ui project.
... is working.

The JS error only occurs on chrome.
Microsoft's Edge browser seems to be ok.

I tried clearing all the chrome cookies.. but still fails.
Any ideas?

Thanks,

image

@nam-pt-do
Copy link
Author

got a bit further... when i added to my SimpleCorsFilter, the content type error went away from chrome.

 response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization");

now i get this from Chrome..

The POST is making it to oauth2-authserver, and it's trying to redirect...

but still the JESSSIONID from /uaa is valid and when i login again... i get the same "Approve/Deny" instead of the /uaa login as i should.

btw, Microsoft Edge works fine... logout, click login, get the uaa login page.

image

@dsyer
Copy link
Collaborator

dsyer commented Sep 5, 2016

It looks like it is working to me (you can change the logout success handler to send a 200 instead of a 302 if you want to get rid of the JS error there, but it is harmless). Maybe the browser is caching something?

@nam-pt-do
Copy link
Author

I finally figured out why chrome wasn't working... and got it to work like Microsoft Edge.

Turns out CORS in chrome is way more strict.

  1. You cannot use wild cards for CORS setting on the filters.
  2. You must explicitly ask to send credentials otherwise chrome will make the POST but wont send the cookies.. so /uaa/logout is meaningless.

So the fix is to modify filters to be more precise with what is allowed and

         response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
         response.setHeader("Access-Control-Allow-Credentials","true");

And on Javascript, you need to ask to send credentials with the AJAX request.

 $.ajax({
            url: "http://localhost:9999/uaa/logout",
            method: "POST",
            xhrFields: {
             withCredentials: true
         },

image

Thanks for all the help.

@pasha-gr
Copy link

Great job Mr nam-pt-do !
I had this request too and a lot of investigations to find a solution, finally I removed sessions from Token Store which caused a lot of uncontrollable errors. yours is more better.
Below I changed your filter code to prevent clients url configuration this way :

        String origin = ((RequestFacade) req).getHeader("origin");
        response.setHeader("Access-Control-Allow-Origin", origin);
        response.setHeader("Access-Control-Allow-Credentials", "true");

It works fine!

@ghost
Copy link

ghost commented Jan 31, 2017

Hi thanks for your posts it helped me a lot. This solution still remembers my last login user so when logout and login for the second time is not prompting me again to provide my credentials it goes straight to approval page. How this can be achieved ?

@dinesh210
Copy link

@nam-pt-do / @dsyer I am using spring boot Oauth2 implementation for authorization and I have similar requirement for logout , I am wondering if /logout end point there in Authorization server the reason I am asking bcz I have enabled authorization server but when I hit /mappings end point I don't see /logout please can someone help me on that should we write the endpoint by ourself of its already available

@dsyer
Copy link
Collaborator

dsyer commented Feb 7, 2017

"/logout" is not normally an MVC endpoint (although you could do it that way). It is provided by Spring Security in a filter.

@nam-pt-do
Copy link
Author

@dinesh210 Here was my fork of the demo sample code, where logout does a CORS post.

 https://github.com/nam-pt-do/tut-spring-security-and-angular-js

One question additional topic I have is that the CORS. How do you make it so that multiple CORS origin URL is allowed.? For example a user may type http://localhost:9090 on the demo, or http://127.0.0.1:9090 or http://:9090 or http://:9090 of machine. All would work, but only the "http://localhost:9090" would correctly logout since it's currently the only origin URL that's registered in CORS filer. I would like to modify the CORS to allow more than one alias origin URL.

@julianobrasil
Copy link

julianobrasil commented Apr 15, 2017

@dinesh210 , see @Pasha-gharibi's comment above.

@ishara
Copy link

ishara commented Apr 27, 2017

How about this solution ? this will remove oauth server session. You need to logout UI Sever and OAuth server both.

@Configuration
@EnableResourceServer
@RestController
public class UserInfoService extends ResourceServerConfigurerAdapter
{
    @Autowired
    FindByIndexNameSessionRepository<? extends ExpiringSession> sessions;

    @RequestMapping("/logout_sso")
    public void logout( Principal principal )
    {
        Collection<? extends ExpiringSession> usersSessions = this.sessions
                .findByIndexNameAndIndexValue(
                        FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
                        principal.getName() )
                .values();
        for( ExpiringSession expiringSession : usersSessions )
        {
            sessions.delete( expiringSession.getId() );
        }

    }
   
    /**/
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .regexMatcher("/logout_sso")
                .authorizeRequests().anyRequest().authenticated();
    }
    }

@dsyer
Copy link
Collaborator

dsyer commented May 2, 2017

@ishara that's a possible solution for some scenarios (it uses Spring Session to share state between multiple apps). There isn't a single solution that would suit everyone.

@demirmustafa
Copy link

@dsyer Hi Dave. I build an auth server by using spring security oauth2 and I used authorization _code grant. Resource server and auth server are spring boot app and i have an angular client.

I wrote custom endpoint for revoking token. When I try to log out, token revoking, so I can't access my resource by using that token(it is normal), but I try to sign in again, auth server give me a new token instead of redirecting me to login page.

@dsyer
Copy link
Collaborator

dsyer commented Jun 7, 2017

@demirmustafa that sounds normal (revoking a token doesn't do anything to the session).

@demirmustafa
Copy link

@dsyer First of all thank you for responding me quickly.
Ok. How I can expire session by using access token ?

@parikmaan
Copy link

parikmaan commented Aug 11, 2017

@nam-pt-do Going through the comments to check if we have a solution for this issue. But was not sure if you found a solution. I took the latest code from master branch. There is no login form presented after user had clicked log out. It goes to approve/deny page after clicking on login button. Just wondering what should be the fix for this issue.

@dsyer
Copy link
Collaborator

dsyer commented Aug 11, 2017

That's the expected behaviour.

@parikmaan
Copy link

@dsyer Thanks for your reply and all other replies above.

'Single logout is subtle, and you have to be careful what you wish for. For instance, if the auth server was Google and the app was your code, I don't think your users would thank you if you logged them out of Google every time they logged out of the app.'

I agree with your statement but there should be a way to log out user from the app (my code). One way is to have separate authentication for my app where user will login with his credentials for the app and then user will log in again with oauth2 credentials (for first time and again if token is invalid). But is there a way where we can use oauth2 authentication for login and log out for the app? Like making oauth2 as a single sign on ?

@alxkalin
Copy link

Isn't this a duplicate or closely related to [Support automatically destroying session on OAuth2 authorization server as soon as user redirected to client app] (spring-attic/spring-security-oauth#140) ?

The workaround provided there by @dsyer seems to work fine.

It's also seems that it would be more correct (if it's really necessary to invalidate session at oauth-server - like in my case and maybe in the case of OP) to invalidate it right after the moment it's not needed any longer and right from auth-server code (not from Zuul, a client app or somewhere else). After successful authentication Zuul initialize it's own session for the user and provide all the resource services behind it with the same access_token stored at the session. It also could automatically update access_token by refresh token with no need in the httpSession (that was created for authentication) at oauth-server. All we need to do when user logs out from the app is to invalidate the session on Zuul, and the pair of corresponding access_token/refresh_token.

But it's not an issue of the tutorial. It's just another subject - "how to transform monolith to microservices and separate authentication out of it to another separate service".

@dsyer
Copy link
Collaborator

dsyer commented Aug 22, 2017

New content now available: https://spring.io/guides/tutorials/spring-security-and-angular-js/#_oauth2_logout_angular_js_and_spring_security_part_ix

@dsyer dsyer closed this as completed Aug 22, 2017
@EtachGu
Copy link

EtachGu commented Jan 3, 2019

New content now available: https://spring.io/guides/tutorials/spring-security-and-angular-js/#_oauth2_logout_angular_js_and_spring_security_part_ix
it was Spring boot 1.5.x , but if I use Spring Boot 2.1.1, how can i sovle the problem Logout

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants