devise: Unauthenticated DELETE /users/sign_out returns 204 status

While working with a gem called devise-jwt, i noticed that an unauthenticated request to DELETE /users/sign_out returns a 204 No-Content.

The same as an authenticated request, whereas an unauthenticated one should return something like 401 Unauthorized.

You can see the issue in detail here https://github.com/waiting-for-dev/devise-jwt/issues/71

Tracking down the problem, it turned out that this problem is actually coming from devise, a plain devise app returns a 204 on an unauthorized request too.

You can try this out with sending a simple DELETE /users/sign_out to a plain devise app without providing a session cookie.

Additionally while testing that, i recognized that if you have a http_basic_authenticate_with in your ApplicationController it returns a 204 too, while it actually should be intercepted before by the basic_auth, or not? This wasn’t an error on my side because maybe the basic_auth was still authenticated by an earlier request. Tried it on a GET request before too and got a 401 and a basic_auth message. After that rejected request i directly tried the DELETE again and it returned a 204 again.

I’m not sure what caused this behaviour of the http_basic_authenticate_with, maybe it’s actually a bug in rails? Not sure if i should open an issue there right now too.

@rafaelfranca since you’re in the rails team and contribute a lot to devise, do you maybe have an idea?

Thanks!

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 18 (3 by maintainers)

Most upvoted comments

Ok that’s good you know about CSRF. 😃 Makes it a lot easier.

Actually it isn’t enough just to pass any valid authenticity_token. The authenticity_token is duplicated and encrypted in the session cookie too, so you have to send a matching session cookie AND matching authenticity_token.

It actually works like this:

Whenever you get a response from your app with rendered html, Rails will include the authenticity_token in your HTML head section via the csrf_meta_tag helper.

This is needed for the unobstrusive javascript adapter to work e.g. making hyperlinks work with non-GET requests, the authenticity token gets send along, as you can see in the console screenshot from the browser request.

This gets also included as a hidden value in every html form.

Additionally this authenticity_token is also encrypted in your session cookie. Rails compares the token in the cookie with the token that gets send along as a parameter. Since only your site knows the token as parameter value, this prevents CSRF from another site where only the cookie gets send along.

So to make a valid authenticated request that doesn’t get intercepted by CSRF countermeasures you have to send along a valid session_cookie ( you can get that from your response headers ) AND a matching authenticity_token which is “buried” somewhere in your html. The problem is that the token always gets randomly recalculated from your secrets as you (re-)load a page. It only works for one request as far as i know, to provide additional security.

So in order to make a working request from an HTTP client you must find a way to grab this token from html and include it in the next request as a parameter AND send the matching session cookie along too. It needs both values to work properly.

A rather dirty workaround would be to disable protect_from_forgery with: :exception in your ApplicationController, but i highly recommend against it.

In your case i would use Postman HTTP Client. There you can setup requests via javascript where you could make a sign_in request, grab your tokens/cookies and include them in the following sign_out request.

If you need more info feel free to ask 😃

Addition:

The problem is that the token always gets randomly recalculated from your secrets as you (re-)load a page. It only works for one request as far as i know, to provide additional security.

I thought things through and I’m not sure if this resembles the facts. Maybe it’s enough to have the same authenticity_token/session_cookie in each request, since rails only compares both. I’m sure it doesn’t keep track of dispatched tokens and if there already have been used. This could make it easier, just grab a token and cookie once and you can use them consecutively.

And I’m not sure if the token gets calculated by or checked against rails secrets. Every random token would suffice i guess. But keep things with a grain of salt. Would have to dig through source to find the exact mechanism.

Just provide a matching cookie/auth token and you should be good to go 😃

Hey @aamarill,

great you’re working on that 😃

Just tested it manually in an http client, in my case https://insomnia.rest/ but you can use whatever client you like ( Postman etc. ). Ii like Insomnia ( because it’s easy to use and it has GraphQL support ) and maybe you’ll like it too 😃 )

I don’t know if you would like to go on search for the problem of your own for learning purposes, but if you get stuck or don’t know where to start, here’s a thing that I identified as the source of the problem(SPOILER ALERT 😃):

I located this line which always returns a 204 No Content header no matter if the user is logged in or not. https://github.com/plataformatec/devise/blob/715192a7709a4c02127afb067e66230061b82cf2/app/controllers/devise/sessions_controller.rb#L79

In the same file you find the destroy action which always calls this method. Didn’t spend much time on it yet to think everything through though, but i guess solving it would be just a conditional for checking if the user isn’t signed in and return another http status then.

If you got questions feel free to ask 😃