After using djangorestframework-jwt in an unsafe way for over year I've finally decided that I would like to get it working in a safer fashion.
I've read everywhere that is not good to save a JWT token in the local client (for example, local storage) and that the best solution is to use HttpOnly cookies instead.
I understood that an HttpOnly cookie is a cookie indeed, that can be saved but not read by the browser. So I thought it could be used like the following:
get_token: the client requests an authorization token to the server by sending user and password: if user and password are valid the server responds with an httpOnly cookie that can be stored but not read by the client.
Every request the client does from now on are authorized because inside the HttpOnly cookie there is a valid authorization token.
refresh_token: once the client needs to refresh the token, it only needs to request a refresh_token: if the sent cookie contains a valid token, the server will respond with an updated HttpOnly cookie with the new token.
I'm now trying to use djangorestframework-jwt by using HttpOnly cookie and the JWT_AUTH_COOKIE configuration seems to be the most fitting one:
You can set JWT_AUTH_COOKIE a string if you want to use http cookies in addition to the Authorization header as a valid transport for the token. The string you set here will be used as the cookie name that will be set in the response headers when requesting a token. The token validation procedure will also look into this cookie, if set. The 'Authorization' header takes precedence if both the header and the cookie are present in the request.
Default is None and no cookie is set when creating tokens nor accepted when validating them.
After giving a string value to JWT_AUTH_COOKIE I received an httpOnly cookie as expected.
The problem:
When I call refreshToken I get the following response:
{"token":["This field is required."]}
True, I'm not sending any token in the request's HEADER and that is what I want since the client isn't supposed to keep it saved anywhere.
And that is where I'm getting confused:
If i'm not wrong from now on every request the client does to the server, the cookie should be added to the request.
Shouldn't the server check the cookie after it sees that no token has been passed in the Header? How is it supposed to work if not like this?
Also posted a Github issue here if anyone wants to contribute for improvements: https://github.com/jpadilla/django-rest-framework-jwt/issues/482
The issue that you observe is correct as the refresh token api has not been implemented with the cookies.
This could be a bug in the code itself. But nothing restrict you from fixing this issue.
You can patch the view to take care of cookie based auth as well. Add below code to the top of your urls.py and it will take care of the same
from rest_framework_jwt.settings import api_settings
if api_settings.JWT_AUTH_COOKIE:
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.serializers import RefreshJSONWebTokenSerializer
from rest_framework_jwt.views import RefreshJSONWebToken
RefreshJSONWebTokenSerializer._declared_fields.pop('token')
class RefreshJSONWebTokenSerializerCookieBased(RefreshJSONWebTokenSerializer):
def validate(self, attrs):
if 'token' not in attrs:
if api_settings.JWT_AUTH_COOKIE:
attrs['token'] = JSONWebTokenAuthentication().get_jwt_value(self.context['request'])
return super(RefreshJSONWebTokenSerializerCookieBased, self).validate(attrs)
RefreshJSONWebToken.serializer_class = RefreshJSONWebTokenSerializerCookieBased
I've added this middleware to my Django (3.1):
class YankTokenRefreshFromHeaderIntoTheBody(MiddlewareMixin):
"""
for Django Rest Framework JWT's POST "/token-refresh" endpoint --- check for a 'token' in the request.COOKIES
and if, add it to the body payload.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, *view_args, **view_kwargs):
if request.path == '/v1/token-refresh' and 'token' in request.COOKIES:
data = json.loads(request.body)
data['token'] = request.COOKIES['token']
request._body = json.dumps(data).encode('utf-8')
return None
Then I added it here in my settings:
MIDDLEWARE = [
'myproj.utils.middleware.YankTokenRefreshFromHeaderIntoTheBody',
...
...
]
And that's it. Django REST framework JWT's token-refresh endpoint will now work as it will find the 'token' key/value in there.
Few things to note:
I chose 'token' as the name of the cookie holding tte JWT token. Yours may vary of course.
I changed the endpoint's name to /v1/token-refresh -- You'd need to change that too if you are using the original named endpoint.
Related
I'm trying to figure out if it is and how possible to initiate a connection with Authorization that avoids getting a 401 back from the server on the initial call.
According to the RFC (https://www.ietf.org/rfc/rfc4559.txt, end of section 4.2), the client should be able to send, with the initial request, an Authorization header containing a token, but for me that doesn't work. I've tried sending the same token (seems to be always the same) that is usually sent as a response to the first 401, but no luck.
Is there something in the configuration that needs to be changed to allow such behavior or do I need a different token?
In Postman Or Jmeter, I want to call a POST API for 100 users in my collection, but after each user login, csrftoken is added to the cookie with the session, but the POST API fails when the cookie contains a csrftoken.
so how to delete the csrftoken from the cookie while maintaining the session ?
Use this as test-script in the postman request. (Note that you have to whitelist your domain in the cookie manager window if you run the collection from postman)
const cookieJar = pm.cookies.jar()
cookieJar.unset("domain.com", "csrftoken", function(error){
})
Refer documentation for more details
You don't need to remove this CSRF token cookie, you need to send correct one.
Not knowing the details of your application it is hard to say what exactly needs to be done, i.e. where the token comes from. If it is being sent by your application in the Set-Cookie header in JMeter it will be enough to add HTTP Cookie Manager
If it comes in the different header or in the response body - you will need to extract it from the previous response using a suitable JMeter Post-Processor and manually add the needed cookie in the HTTP Cookie Manager.
More information: How to Load Test CSRF-Protected Web Sites
I want to customise OAuth Endpoint URI's.
I want to sent parameters in post body instead of query params.
now my request is like -
example.com/oauth/token?grant_type=password&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&username={USERNAME}&password={PASSWORD}
But I want it like this.
example.com/oauth/token
Request body -
{
grant_type=password,
client_id={CLIENT_ID},
client_secret={CLIENT_SECRET},
username={USERNAME},
password={PASSWORD}
}
How should I do it?
The token endpoint of a properly-implemented authorization server does NOT accept GET requests because RFC 6749, "3.2. Token Endpoint" says as follows:
The client MUST use the HTTP "POST" method when making access token requests.
So, your authorization server's token endpoint should reject GET requests.
RFC 6749, "4.3. Resource Owner Password Credentials Grant" says that request parameters of a token request using Resource Owner Password Credentials flow should be embedded in the request body in the format of "application/x-www-form-urlencoded". The following is an excerpt from "4.3.2. Access Token Request".
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=johndoe&password=A3ddj3w
Therefore, you don't have to customize your authorization server. If the server is implemented correctly, its token endpoint accepts POST requests.
The token endpoint created by spring-oauth2 already deals with POST as well.
It would be hard to customize it to accept a JSON request body, because the TokenEndpoint class expects all the params as #RequestParam params.
However, if your concern is about security (as HTTPs does not secure query parameters) you indeed can send the request parameters through post. It is just a matter of sending the request in the form "form-data" or "x-www-form-urlencoded". These are 2 ways of sending arbitrary key-value parameters in the request body, in a way that appears to the server as they are regular request parameters. So it is a matter of making your client using this.
Also, note that in spring-oauth2 it is possible to disable the GET endpoint, this way forcing your clients to use POST with one of the ways above.
I am using polymer to send ajax requests to my Drupal services api.
I send a POST to login and then a POST to create a node. When I login I am given a token which I store and pass to the next request.
I am monitoring the the requests and responses with Charles, the token is being sent, the cookie is being set and passed on the 2nd POST but I get an "Unauthorized : CSRF validation failed" response.
When I send the request with Postman It works like a dream but for some reason it doesn't validate when sent with my app.
I have checked the token being set matches the one being sent and the only difference I've noticed is that when it's being sent again there is a prefix of ga_; something to do with google analytics?
The expiry of the token is a month away the token matches what is returned at login and is being sent correctly. The header accepts X-CSRF-Token in the Access-Control-Allow-Headers.
My CORS module code is:
api/*|<mirror>|GET, POST, PUT, OPTIONS|Authorization, Origin, Content-Type, X-CSRF-Token|true
If any body has a similar issue, mine was caused by a couple of things, running Drupal and my app in the same browser causing all kinds or cookie conflicts and when passing parameters to my function that computes my request, if there were any parameters that were not used it breaks.
Hope this helps someone.
I am dealing with oauth 1.0 (twitter and flickr). Website works at port 80 and oauth server works at port 8080
Algorithm:
send ajax request to oauth server to check if user have valid access_token
open authorization window if user have no access_token or access_token is expired
save access_token in user's session at the oauth server
send sharing data to the oauth server
It uses sinatra + rack:session + rack::session::sequel + sqlite to store sessions. It sends Set-Cookie: rack.session=id in each response
I am using 2 types of request: crossdomain ajax with jquery and usual request with window.open. I have a big security problem passing cookies to crossdomain ajax request.
No matter that server's response headers contains
Access-Control-Allow-Headers: *
chromium will throw security error:
Refused to set unsafe header "Cookie"
I want to avoid this problem by passing rack.session=id to post data and load it:
before "/twitter/connect.json" do
session = Rack::Session::something(params["rack.session"])
end
But I cant find in documentation how to do this
Rack::Session::Abstract::ID has an option called cookie_only that allows the session id to be passed in via the params. However, it defaults to true, and most session middleware implementations don't bother to override it.
Your best bet is probably to monkey patch Rack::Session::Abstract::ID to default cookie_only to false.
Rack::Session::Abstract::ID::DEFAULT_OPTIONS.merge! :cookie_only => false