Skip to content
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

Post-logout redirect never fires #93

Closed
shacker opened this issue Mar 21, 2017 · 63 comments
Closed

Post-logout redirect never fires #93

shacker opened this issue Mar 21, 2017 · 63 comments

Comments

@shacker
Copy link

shacker commented Mar 21, 2017

I was planning to submit a PR here today, but I've hit a roadblock. Setup:

We use django_cas_ng and our users auth against a CAS SSO system. When they click the Logout link on our site, they are logged out of the site AND redirected to our campus SSO system's logout page, which kills their ticket. This is important, especially for multi-user lab computers.

After installing django-session-security, clicking the Logout link manually still works normally. But if I let a user time out with DSS, they are logged out but they are NOT redirected to the SSO logout view. They stay on the site. In this state, the user can click the Login link again and be logged in automatically again without having to authenticate (because the CAS session ticket is still alive). That's bad.

So I started a PR that lets the dev set a custom logout URL. If present, the middleware.py adds a simple redirect after logout():

        if delta >= timedelta(seconds=expire_seconds):
            logout(request)
            return HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)

(this is in process_request()). The problem is that the redirect never happens after timeout - the user is logged out but the page is not redirected to settings.LOGOUT_REDIRECT_URL. I don't understand why.

If I modify it to go to the CAS logout page without performing an internal logout first:

        if delta >= timedelta(seconds=expire_seconds):
            return HttpResponseRedirect(settings.LOGOUT_REDIRECT_URL)

Then a timeout logout does redirect, but if the user then tries to go back to the site (e.g. to log in as someone else), they're stuck in a loop eternally handing off to settings.LOGOUT_REDIRECT_URL, so they can't access the site at all.

I can't seem to make this work either way. Any idea what I'm missing here? It seems clear that No. 1 is what I want, but I can't figure out why the redirect never fires.

n.b. I also have code to call django_cas_ng's logout() function rather than Django's, but that doesn't affect the problem - it's the same either way.

@jpic
Copy link
Member

jpic commented Mar 21, 2017

(this is in process_request()). The problem is that the redirect never happens after timeout - the user is logged out but the page is not redirected to settings.LOGOUT_REDIRECT_URL. I don't understand why.

Perhaps because it's an ajax request ? Maybe the fix also involves a tiny bit of JS ;)

I'm not really sure about problem 2, perhaps an example app in the test project would help ?

@shacker
Copy link
Author

shacker commented Mar 21, 2017

Maybe I'm not following how this works - are you saying that this python is converted to ajax? Our logout system is not doing any ajax.

Not sure we need a full test app here - testing is as simple as adding two lines to middleware.py:

        if delta >= timedelta(seconds=expire_seconds):
            print("About to log out")
            logout(request)
            print("Just logged out")

Then let it time out. The first print statement will fire, the second will not. Or am I misunderstanding?

@jpic
Copy link
Member

jpic commented Mar 21, 2017 via email

@shacker
Copy link
Author

shacker commented Mar 21, 2017

Ah, I wasn't clear whether you were suggesting that our SSO logout app was ajax-based or whether you meant the ajax aspects of DSS. I'm aware that DSS is doing ajax exchange, but it's not doing it at this point in the code, which is all Python, right?

So it's still not clear to me - why doesn't any python after logout() fire here? Is there another way I can handle this?

@shacker
Copy link
Author

shacker commented Mar 21, 2017

Django's auth.logout() does not swallow exceptions, nor does django_cas_ng's, and we've never had a problem with logout crashing. There are no 500s in runserver console or in browser console indicating any problem there, so I don't think logout is raising an exception.

@jpic
Copy link
Member

jpic commented Mar 22, 2017 via email

@shacker
Copy link
Author

shacker commented Mar 22, 2017

OK, I've just had a very long session trying to debug this. Starting with pdb in middleware.py, then injecting pdb - and print statements - directly into Django's logout function, stepping through with n, then a much deeper dive with s, watching every step of the way and trying to figure out where it's going wrong. I just cannot figure out where it's getting derailed. No errors - It just doesn't redirect.

To simplify, I'm leaving django_cas_ng out of the picture and just dealing with django's logout function.

I then tried another approach. Realized that you're doing:

from django.contrib.auth import logout
but if you use the other logout:
from django.contrib.auth.views import logout

you can pass in next_page='http://example.com'

So rather than

from django.contrib.auth import logout
logout(request)
return HttpResponseRedirect('http://example.com')

I tried:

from django.contrib.auth.views import logout
logout(request, next_page="http://example.com")

No difference - same problem.

To make sure there is nothing odd going on in my somewhat complex project, I then did a fresh pip install into a new/clean Django project, and tried the same modifications to middleware.py. Same results.

This is a weird one. Quite a rabbit hole, and I'm running out of ideas.

Are you able to reproduce this?

@shacker
Copy link
Author

shacker commented Mar 22, 2017

FWIW here's the tail end of a pdb session, just before and after where it claims to be doing the redirect:

> /path/to/python3.5/http/cookies.py(430)OutputString()
-> for key, value in items:
(Pdb) 
--Return--
(Pdb) > /path/to/venv/lib/python3.5/site-packages/django/contrib/staticfiles/handlers.py(63)__call__()-><HttpResponse...u/cas/logout">
-> return self.application(environ, start_response)

> /path/to/python3.5/http/cookies.py(431)OutputString()
-> if value == "":
(Pdb) 
(Pdb) > /path/to/python3.5/wsgiref/handlers.py(138)run()
-> self.finish_response()

> /path/to/python3.5/http/cookies.py(432)OutputString()
-> continue
(Pdb) 
(Pdb) [22/Mar/2017 14:33:37] "GET /session_security/ping/?idleFor=25&_=1490216704426 HTTP/1.1" 302 0
--Return--
> /path/to/python3.5/wsgiref/handlers.py(138)run()->None
-> self.finish_response()

> /path/to/python3.5/http/cookies.py(430)OutputString()
-> for key, value in items:(Pdb) 
[22/Mar/2017 14:33:38] "GET /people/ HTTP/1.1" 200 59895

@shacker
Copy link
Author

shacker commented Mar 23, 2017

To be clear on my goal here: If I can successfully call django_cas_ng's logout() function, it should handle the redirect. But I can't call it, just as I can't call a redirect after Django's own logout func. The experiments above with manual redirects are because I'm trying alternative approaches.

@shacker
Copy link
Author

shacker commented Mar 24, 2017

We're going to to take another approach to this and not use django-session-security, but thanks for your time.

I'm still curious about the solution to this if anyone is able to figure it out.

@santosh9991
Copy link

@shacker: I have the same issue. django_cas_ng logout doesn't get fired on Ajax request on timeout.
We use angular JS, django-1.9.7 and CAS for authentication. We wanted to include auto logout functionality .I have read through the comments. However, I coudn't figure-out the solution. I was wondering whether you found a solution. I have tried debugging in many ways. It works on on-click though. Can you please respond. Any suggestions are appreciated.

@claytondaley
Copy link
Contributor

claytondaley commented Nov 16, 2017

If you call logout from a view, you would normally return the response. As you can see here (links to an older version of Django because the interface recently changed), one of the possible responses conveys redirect information. As you saw, the middleware does not return the response. All it does is trigger the database-side processing for the logout.

But the "answer" isn't to change session security because the application cannot be responsible for timing out the SSO credential... the SSO server needs to do that. Consider the simplest case:

  • User A logs into the application
  • User A closes the browser window holding the application (there's no opportunity to redirect, even if supported)
  • User B opens a new window and goes to email

Because User A's SSO session is still alive, User B is routed to the User A's email. There's nothing that session security can do to take care of this. The SSO server needs to timeout the SSO session.

The application also needs a timeout so application sessions don't outlast the SSO sessions (by more than the timeout), but session security already handles this.

@santosh9991
Copy link

@claytindaley:
Thanks for taking your precious time and responding back.
Thanks for above mentioned solution. I have couple of questions on the suggestions you made above:
1.Will the user be logged out abruptly after the timeout('X' amount of time) configured in SSO server. For example, if user A is logged in and he has been using the application for 10 mins. Would the User A be logged out after 10 minutes session time, abruptly even if the User A is using the application continuously?
Below is my actual issue that I am concerned about:

  1. cas_logout view is successfully called by Ajax http get request on timeout. However, user is not logged out. On page reload, user would automatically logged in.
    My understanding was, when ever cas_logout view is called user should be logged out and redirected to cas login page.
    2.Same view when called using href(i.e on-click of sign-out button manually), it works perfectly fine. user is logged-out and redirected to cas login page
    My question is why does the same view behave differently, when fired through Ajax http call and on-click (through href).

@claytondaley
Copy link
Contributor

Let me start by clarifying that I don't use CAS so I'm referring to SSO principles in general.

The application probably works one of two ways (determined by the plugin that manages Django auth):

  1. It authenticates once (initially) - you will remain logged into the application so long as you don't timeout within the application
  2. It reauthenticates periodically - if the SSO session ends, you will be prompted to re-authenticate at some point

However, you would expect the timer on the SSO session to be reset each time the application reauthenticates. It follows that you shouldn't be abruptly terminated by either approach (unless the SSO timeout is shorter than the application reauth time).

@claytondaley
Copy link
Contributor

cas_logout view is successfully called by Ajax http get request on timeout. However, user is not logged out. On page reload, user would automatically logged in

Can you screenshot the logout call that that works? The screenshot you provided shows a 403 (permission denied) error. I assume the logout wasn't successful (which would explain why the subsequent login worked). Compare the two URLs and make sure they are exactly the same.

@santosh9991
Copy link

Please ignore CAS Middleware and some other print statements. I have added print statements for debugging.

@claytondaley
Copy link
Contributor

If you're using Chrome's developer tools, use this option to retain a log of all of the redirects during a logout:

image

Click the manual logout button and screenshot the entry that successfully logs the user out of the SSO server. I want to confirm that you don't get a 403 on that redirect... and hopefully you can compare the URL and find a difference.

@claytondaley
Copy link
Contributor

I assume the two 403 calls are against the IP/domain of your SSO server, but maybe you should verify that as well.

@claytondaley
Copy link
Contributor

claytondaley commented Nov 17, 2017

Oh I assumed that was the AJAX not the logout button. So that's what happens when you press logout... and the user is successfully logged out (of the SSO server)? Or that's what happens when you press the logout button and it doesn't work?

@santosh9991
Copy link

This happens when AJAX call is made from front end after timeout(Auto time-out), Ref. Fig:4 which I had mentioned in previous comments.
image
Fig6: Browser logs for AJAX request.
User logs out perfectly fine when logout button is pressed manualy.

@claytondaley
Copy link
Contributor

User logs out perfectly fine when logout button is pressed manualy.

Yes. I'm asking you to snapshot the browser history when the user manually logs out. I want to compare those calls to the AJAX calls. I'm betting something is different.

@claytondaley
Copy link
Contributor

Can you scroll up and find the call to the actual SSO server? The tail end of the logs look to be the information about the current page (js libraries and such).

@claytondaley
Copy link
Contributor

claytondaley commented Nov 17, 2017

You're looking for a call like you see in the AJAX... something like "logout/?service=..."

@claytondaley
Copy link
Contributor

I mean scroll up in the browser history... in the chrome developer tools window you just screenshoted.

@santosh9991
Copy link

SSO logs are not saved for some reason in browser

@santosh9991
Copy link

image
No logs are shown in the chrome browser when user logs out(on-click through href, Ref Fig 5 in above comments) without any issues. I see browser logs as mentioned in Fig 6. when user does not logout on AJAX call(Ref code in Fig. $)

@claytondaley
Copy link
Contributor

claytondaley commented Nov 17, 2017

No logs are shown in the chrome browser when user logs out(on-click through href, Ref Fig 5 in above comments) without any issues.

You should at least see the call to URLs like (obviously using your domain/IP) djangoapp.com/logout followed by ssoserver.com/login Try this sequence (leaving preserve logs on):

  • log into your application
  • click "clear" logs
    image
  • click logout link in app
  • (once the browser gets to the login screen) scroll to the absolute top of your chrome network logs and screenshot

You should definitely see at least the call to djangoapp.com/logout followed by some sequence that ends at ssoserver.com/login.

@claytondaley
Copy link
Contributor

You need to disable the filter. You see the red filter icon:

image

It's happening because you've clicked "XHR". If you unclick that icon (XHR or the filter entirely), you should get a full history.

@santosh9991
Copy link

santosh9991 commented Nov 17, 2017

image

Fig 12: Browser logs on successful logout

@claytondaley
Copy link
Contributor

That says "login". Any chance you didn't clear the logs before clicking logout?

@claytondaley
Copy link
Contributor

claytondaley commented Nov 18, 2017

Hmm. It looks like there are some headers in your request detail. Can you compare the headers in the successful and unsuccessful request and see if there are any differences?

@santosh9991
Copy link

You mean request headers?

@claytondaley
Copy link
Contributor

Yes. The SO question was suggesting that an XHR request may not carry the right headers. That seemed like the next most-obvious thing to compare. I'd look at the headers first and then the cookies. If you were missing authentication information in one of these places, it would explain your issue.

@jpic
Copy link
Member

jpic commented Nov 18, 2017

Also did you try debugging the logout view ? You could also print the request headers from there.

@claytondaley
Copy link
Contributor

This is starting to sound like a CSRF (cross site request forgery) issue. You're allowed to send the request using the browser, but prohibited from sending the request using JavaScript.

@santosh9991
Copy link

@claytondaley : As you mentioned, it should be with the request header. Below are the screen shots of request headers for AJAX and normal href(onclick request) browser logs
image
Fig 15: request header for AJAX call(which we have issue with)
image
Fig 16: request header through on-click logout(which works fine)

@claytondaley
Copy link
Contributor

Fig 16 says it's "login" not logout

@santosh9991
Copy link

@claytondaley : I tried replacing the context-type and datatype in the AJAX call, but no luck
image

Fig 19: Modified AJAX call code

@claytondaley
Copy link
Contributor

claytondaley commented Nov 18, 2017

OK. (The relevant screenshot appears to have been deleted but) It looks like the cookies are missing from the call that fails (and present in the call that succeeds). That makes sense as authentication information is often carried in cookies.

@santosh9991
Copy link

image
Fig 20: browser logs when I replace the AJAX content-Type to "application/json; charset=utf-8"

@santosh9991
Copy link

image
Fig 21: Request header logs in browser for AJAX logout request after autotimeout

@claytondaley
Copy link
Contributor

I don't think the content type is what matters. I think you need to get the cookies into the request. For CSRF reasons, I'm not sure the browser will ever do so automatically. I suggest trying to replace the AJAX call with a full page redirect.

@santosh9991
Copy link

image
@claytondaley : You want me to include cookies in the AJAX request

@claytondaley
Copy link
Contributor

You want me to include cookies in the AJAX request

I think it's failing because the authentication cookies are missing. Even if you wanted to fix it, I'm not sure if you have access to the right cookies in your JavaScript (for security reasons). If you can get the right cookies and get them included... that would be great.

If you can't, I'm suggesting changing the way the redirect is handled.

@claytondaley
Copy link
Contributor

If you can't, I'm suggesting changing the way the redirect is handled.

If this solves your issue, we may want to consider a change to session security, but I'd want you to confirm that it's working for you first.

@santosh9991
Copy link

image
What changes should be made?

@claytondaley
Copy link
Contributor

What changes should be made?

I'm honestly not sure. If you look here, there's already code to do a full page redirect. If this.returnToUrl was set to your logout page, it seems like it should be working fine.

Can you show me the code where you instantiate the sessionSecurity JS script? For example:

{% include 'session_security/all.html' %}

or

        <script type="text/javascript">
            var sessionSecurity = new yourlabs.SessionSecurity({
                pingUrl: '{% url 'session_security_ping' %}',
                warnAfter: {{ request|warn_after|unlocalize }},
                expireAfter: {{ request|expire_after|unlocalize }},
                confirmFormDiscard: "{% trans 'You have unsaved changes in a form of this page.' %}"
            });
        </script>

@claytondaley
Copy link
Contributor

@jpic, do we need to add returnToUrl: ... here? That may explain why the OP complained that:

the page is not redirected to settings.LOGOUT_REDIRECT_URL

Obviously, the SSO server should also have a timeout, but it definitely sounds like the OP was properly configured for the behavior he (and @santosh9991) needs.

@jpic
Copy link
Member

jpic commented Nov 19, 2017

You're making it happen <3

You have all the holy blessings you could daydream of to hack the source code.

May the source be withy ou.

@claytondaley
Copy link
Contributor

@santosh9991 can you try the code in PR #101. All you should need to do is add the setting SESSION_SECURITY_REDIRECT_TO_LOGOUT = True

@santosh9991
Copy link

@claytondaley Will try and let you know

@claytondaley
Copy link
Contributor

@santosh9991 Don't bother testing yet. I got around to writing a test case and have some issues. I'll let you know when they're resolved and push an update.

@claytondaley
Copy link
Contributor

OK test cases now pass so @santosh9991, you should be good to go. You'll need to add SESSION_SECURITY_REDIRECT_TO_LOGOUT = True to your application settings, but the code in the PR should do the rest of the work for you.

@santosh9991
Copy link

@claytondaley : Is the latest build available on pip? if not, can you please guide me where to pull the code from.

@claytondaley
Copy link
Contributor

If you pull from https://github.com/claytondaley/django-session-security you should get a redirect-to-logout branch. That's the PR branch.

@claytondaley
Copy link
Contributor

@santosh9991 Did you test the PR? Did it work? If so, please let us know so we can consider merging it.

@santosh9991
Copy link

@claytondaley : It worked in the production. Thank you for the help.

@jpic
Copy link
Member

jpic commented Apr 12, 2024

This has been merged to master

@jpic jpic closed this as completed Apr 12, 2024
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

4 participants