I'm trying to use django-simplejwt & restframework with RSA256. I generated my keypair by following this gist. The public & private keypair are being saved in a .env file:
JWT_SIGNING_KEY='-----BEGIN RSA PRIVATE KEY-----\\n
keydata\\n
-----END RSA PRIVATE KEY-----'
JWT_VERIFYING_KEY='-----BEGIN PUBLIC KEY-----\\n
keydata\\n
-----END PUBLIC KEY-----'
My jwt configuration settings.py looks like this (additional info is for compatibility with Auth0):
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
SIGNING_KEY = os.getenv("JWT_SIGNING_KEY")
VERIFYING_KEY = os.getenv("JWT_VERIFYING_KEY")
SIMPLE_JWT = {
'USER_ID_FIELD': 'user_id',
'ACCESS_TOKEN_LIFETIME': timezone.timedelta(minutes=10),
'REFRESH_TOKEN_LIFETIME': timezone.timedelta(hours=6),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'ALGORITHM' : 'RS256', # Default would be HS256
'SIGNING_KEY' : f'''{SIGNING_KEY}''', #config("JWT_SIGNING_KEY"), # Must be private key
'VERIFYING_KEY' : f'''{VERIFYING_KEY}''', #config("JWT_VERIFYING_KEY"), # Must be public key
'AUDIENCE' : config("JWT_AUDIENCE"),
'ISSUER': config("JWT_ISSUER"),
'JWK_URL': f'{config("JWT_ISSUER")}/.well-known/jwks.json',
'USER_ID_CLAIM': f'{config("JWT_AUDIENCE")}/email',
'JTI_CLAIM': None,
'TOKEN_TYPE_CLAIM': None,
'AUTH_HEADER_TYPES': ('Bearer',),
}
When restricted views that need a user object are being called, a function for decoding the jwt token and returning the corresponding user object is being executed:
def token_user(request):
s, token = request.META["HTTP_AUTHORIZATION"].split(" ")
decoded = jwt.decode(token, settings.VERIFYING_KEY, algorithms=["RSA256"])
username = decoded["username"]
user = get_user_model().objects.filter(username=username)
return user
Here are my Login view & LoginSerializer (I cut out most of the validation process since this worked before implementing jwt. I left the part generating the token):
#views.py
class Login(TokenObtainPairView):
serializer_class = LoginSerializer
#serializers.py
class LoginSerializer(TokenObtainPairSerializer):
username_field = get_user_model().USERNAME_FIELD
password = serializers.CharField(write_only=True, required=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields[self.username_field] = serializers.CharField()
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token['username'] = user.username
return token
def validate(self, attrs):
....
refresh = self.get_token(user)
attrs["refresh"] = str(refresh)
attrs["access"] = str(refresh.access_token)
....
When trying out the Login I get the following Error:
TypeError: Expecting a PEM-formatted key.
Related
I'm working on a project where user can register using his mobile no and password( after verifying with otp) what i'm doing is inside username field i'm saving user's phone no. as username is a mandatory field. And I'm using simple_jwt to get access token and refresh token. Everything is working fine
urls.py
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns =[
path('register', RegisterView.as_view() ),
path('otp/send', SendOtpView.as_view() ),
path('otp/verify', VerifyOtpView.as_view() ),
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
instead of creating a loginSerializer i'm using rest_framework_simplejwt inbuild class TokenObtainPairView
But when i go to the url
auth/api/token/ it ask me for username and password, which is confusing as a user . how can i change the name of the username to phone.
I don't have any idea how to do it as I'm new to the djangorestframework
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class LoginSerializer(TokenObtainPairSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
**phone = serializers.CharField(max_length=20, source='username')**
class Meta:
model = User
fields = ['phone', 'password']
I tried doing this but then it add on another field with the name phone instead of replacing username. I even don't know whether it will work or not .
Ok. Somehow i found a way to do it. I created a model serializer an used an custom token generator method
https://django-rest-framework-simplejwt.readthedocs.io/en/latest/creating_tokens_manually.html
serializers.py
# for creating token manually
def get_tokens_for_user(user):
refresh = RefreshToken.for_user(user)
return {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
class LoginSerializer(serializers.ModelSerializer):
password = serializers.CharField(max_length=65, min_length=8, write_only=True)
phone = serializers.CharField(max_length=20, source='username')
tokens = serializers.SerializerMethodField(read_only=True)
def get_tokens(self, obj):
return obj['tokens']
class Meta:
model = User
fields = ['phone', 'password', 'tokens']
def validate(self, attrs):
param_phone = attrs.get('username', "")
param_password=attrs.get('password', '')
if not param_phone.isnumeric():
raise serializers.ValidationError(
{'phone': ("Only Phone Number is accepted")}
)
if not len(param_phone) == 10:
raise serializers.ValidationError(
{'phone': ("Phone Number length can only be of 10 digits")}
)
user = auth.authenticate(username=param_phone, password=param_password)
if User.objects.filter(username=param_phone, is_active=False).exists():
raise AuthenticationFailed("Account disabled. Contact admin")
if not user:
raise AuthenticationFailed("Invalid Credential. Try again")
tokens = get_tokens_for_user(user)
return {
'username':param_phone,
'tokens': tokens
}
return super().validate(attrs)
views.py
class LoginView(GenericAPIView):
serializer_class = LoginSerializer
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
return Response(serializer.data, status=status.HTTP_200_OK)
I'm using all-auth and dj-rest-auth to implement registration and login via email. Everything work fine but when making test, inactive users return invalid credentials message instead inactive account message. In the LoginSerializer, it seems that django.contrib.auth authenticate method doesn't return the user, but None. Here is the code:
settings.py
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.AllowAllUsersModelBackend",
"allauth.account.auth_backends.AuthenticationBackend"
]
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'user.serializers.RegisterSerializer',
}
REST_AUTH_SERIALIZERS = {
'LOGIN_SERIALIZER': 'user.serializers.LoginSerializer',
'USER_DETAILS_SERIALIZER': 'user.serializers.UserDetailSerializer',
}
serializers.py
class LoginSerializer(serializers.Serializer):
email = serializers.EmailField(required=True, allow_blank=False)
password = serializers.CharField(style={'input_type': 'password'})
def authenticate(self, **kwargs):
return authenticate(self.context['request'], **kwargs)
def _validate_email(self, email, password):
user = None
if email and password:
user = self.authenticate(email=email, password=password)
else:
msg = _('Must include "email" and "password".')
raise exceptions.ValidationError(msg)
return user
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
user = None
if 'allauth' in settings.INSTALLED_APPS:
from allauth.account import app_settings
# Authentication through email
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
user = self._validate_email(email, password)
# Did we get back an inactive user?
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
# If required, is the email verified?
if 'dj_rest_auth.registration' in settings.INSTALLED_APPS:
from allauth.account import app_settings
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
try:
email_address = user.emailaddress_set.get(email=user.email)
except:
raise serializers.ValidationError(_('E-mail is not registered.'))
else:
if not email_address.verified:
raise serializers.ValidationError(_('E-mail is not verified.'))
attrs['user'] = user
return attrs
tests.py
########################################################################
# LOG IN WITH INACTIVE USER
login_data = {
'email': 'inactive#inactive.com',
'password': '9I8u7Y6t5R4e'
}
response = self.client.post('http://localhost:8000/api/auth/login/', login_data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
expected_error = {
'non_field_errors': 'User account is disabled.'
}
response_error = {
'non_field_errors': response.data['non_field_errors'][0]
}
self.assertEqual(response_error, expected_error)
Is there something that I missing?
Thanks in advance.
In case anyone is interested, I found the problem: the allauth authentication backend override the django model backend. In order to resolve this, I create a class that inherit from allauth backend and add the function that allow all users to log in:
backend.py
from allauth.account.auth_backends import AuthenticationBackend
class AllowAllUsersModelBackend(AuthenticationBackend):
def user_can_authenticate(self, user):
return True
Then add it to settings:
settings.py
AUTHENTICATION_BACKENDS = [
"user.backends.AllowAllUsersModelBackend",
]
As i am using a third party package called djoser to handle the token authentication. i want to customise the response. But after trying to change it, the result is not what i wanted.
I just wanted to get the token value of the token instead of having "auth_token:" in front.
Here is the link to djoser: https://github.com/sunscrapers/djoser
Here is my code :
serializer.py
class TokenCreateSerializer(serializers.Serializer):
password = serializers.CharField(
required=False, style={'input_type': 'password'}
)
default_error_messages = {
'invalid_credentials': constants.INVALID_CREDENTIALS_ERROR,
'inactive_account': constants.INACTIVE_ACCOUNT_ERROR,
}
def __init__(self, *args, **kwargs):
super(TokenCreateSerializer, self).__init__(*args, **kwargs)
self.user = None
self.fields[User.USERNAME_FIELD] = serializers.CharField(
required=False
)
def validate(self, attrs):
self.user = authenticate(
username=attrs.get(User.USERNAME_FIELD),
password=attrs.get('password')
)
self._validate_user_exists(self.user)
self._validate_user_is_active(self.user)
return attrs
def _validate_user_exists(self, user):
if not user:
self.fail('invalid_credentials')
def _validate_user_is_active(self, user):
if not user.is_active:
self.fail('inactive_account')
views.py
class TokenCreateView(utils.ActionViewMixin, generics.GenericAPIView):
"""
Use this endpoint to obtain user authentication token.
"""
serializer_class = settings.SERIALIZERS.token_create
permission_classes = [permissions.AllowAny]
def _action(self, serializer):
token = utils.login_user(self.request, serializer.user)
token_serializer_class = settings.SERIALIZERS.token
return Response(
data=token_serializer_class(token).data,
status=status.HTTP_200_OK,
)
my custom views.py
class CustomTokenCreateView(utils.ActionViewMixin, generics.GenericAPIView):
"""
Use this endpoint to obtain user authentication token.
"""
serializer_class = TokenCreateSerializer
permission_classes = [permissions.AllowAny]
def _action(self, serializer):
token = utils.login_user(self.request, serializer.user)
token_serializer_class = settings.SERIALIZERS.token
content = {
'Token': token_serializer_class(token).data,
'promptmsg': 'You have successfully login',
'status': '200'
}
return Response(
data=content,
status=status.HTTP_200_OK,
)
Result from djoser token authentication:
Success :
{
"auth_token": "da57cd11c34cb4332eaa6cc2cac797d0ee95cafb"
}
Result from my edited djoser authentication
Success:
{
"Token": {
"auth_token": "da57cd11c34cb4332eaa6cc2cac797d0ee95cafb"
},
"promptmsg": "You have successfully login",
"status": "200"
}
The result i want
Success:
{
"Token": "da57cd11c34cb4332eaa6cc2cac797d0ee95cafb",
"promptmsg": "You have successfully login",
"status": "200"
}
Is there a way to remove the auth_token tag ? i do not mind if its auth_token but as long as the format is what it is expected
Try this to access the value:
content = {
'Token': token_serializer_class(token).data["auth_token"],
'promptmsg': 'You have successfully login',
'status': '200'
}
That way you are not assigning the entire object.
I'm working on a project to enable the django rest framework authentication for mobile devices. I'm using the default token authentication for get the user token from a post request sending username and password.
curl --data "username=username&password=password" http://127.0.0.1:8000/api/api-token-auth/
(api/api-token-auth/ is the url configured with the obtain_auth_token view)
urlpatterns = [
url(r'^api/api-token-auth/', obtain_auth_token),
url(r'^', include(router.urls)),
]
and the response is the user token.
{"token":"c8a8777aca969ea3a164967ec3bb341a3495d234"}
I need to obtain the user token auth using email-password on the post instead username-password, or both. I was reading the documentation of custom authentication http://www.django-rest-framework.org/api-guide/authentication/#custom-authentication... but really, isn't very clear to me.
It's very helpful to me... thanks :).
Ok,I found a way for get the auth token using email or username... This is the serializer:
class AuthCustomTokenSerializer(serializers.Serializer):
email_or_username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
email_or_username = attrs.get('email_or_username')
password = attrs.get('password')
if email_or_username and password:
# Check if user sent email
if validateEmail(email_or_username):
user_request = get_object_or_404(
User,
email=email_or_username,
)
email_or_username = user_request.username
user = authenticate(username=email_or_username, password=password)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
else:
msg = _('Must include "email or username" and "password"')
raise exceptions.ValidationError(msg)
attrs['user'] = user
return attrs
In the email_or_username field, the user can send the email or the username, and using the function validateEmail(), we can check if the user is trying to login using email or username. Then, we can make the query for get the user instance if is valid, and authenticate it.
This is the view.
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (
parsers.FormParser,
parsers.MultiPartParser,
parsers.JSONParser,
)
renderer_classes = (renderers.JSONRenderer,)
def post(self, request):
serializer = AuthCustomTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
content = {
'token': unicode(token.key),
}
return Response(content)
and then:
curl --data "email_or_username=emailorusername&password=password" http://127.0.0.1:8000/api/my-api-token-auth/.
It's ready.
Write these requirements into your settings.py
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
To check, send this json format request to your server:
{
"username":"youremail#mail.domain",
"password":"Pa$$w0rd"
}
Change the default serializer the library is using for example in auth/serializers.py
from django.contrib.auth import authenticate
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
class MyAuthTokenSerializer(serializers.Serializer):
email = serializers.EmailField(label=_("Email"))
password = serializers.CharField(
label=_("Password",),
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
if email and password:
user = authenticate(request=self.context.get('request'),
email=email, password=password)
# The authenticate call simply returns None for is_active=False
# users. (Assuming the default ModelBackend authentication
# backend.)
if not user:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "email" and "password".')
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
Override the view for example in auth/views.py
from rest_framework.authtoken import views as auth_views
from rest_framework.compat import coreapi, coreschema
from rest_framework.schemas import ManualSchema
from .serializers import MyAuthTokenSerializer
class MyAuthToken(auth_views.ObtainAuthToken):
serializer_class = MyAuthTokenSerializer
if coreapi is not None and coreschema is not None:
schema = ManualSchema(
fields=[
coreapi.Field(
name="email",
required=True,
location='form',
schema=coreschema.String(
title="Email",
description="Valid email for authentication",
),
),
coreapi.Field(
name="password",
required=True,
location='form',
schema=coreschema.String(
title="Password",
description="Valid password for authentication",
),
),
],
encoding="application/json",
)
obtain_auth_token = MyAuthToken.as_view()
Hook up the url for example in auth/urls.py
from .views import obtain_auth_token
urlpatterns = [
re_path(r'^api-token-auth/', obtain_auth_token),
]
and you are ready to go!!
There is a cleaner way to get the user token.
simply run manage.py shell
and then
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
u = User.objects.get(username='admin')
token = Token.objects.create(user=u)
print token.key
Okey so I wish to create really simple user managment for my site using django(1.6), tastyie and angularjs. I want to be able to signin/up/out. I have basically implemented the solution from here: How can I login to django using tastypie
my code looks like this:
resources:
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from photod.models import Project, ProjectImage
from tastypie.authorization import DjangoAuthorization,Authorization
from tastypie.authentication import BasicAuthentication,Authentication
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from tastypie import fields
from tastypie.serializers import Serializer
from tastypie.exceptions import BadRequest
from django.db import IntegrityError
from tastypie.http import HttpUnauthorized, HttpForbidden
from django.conf.urls import url
from tastypie.utils import trailing_slash
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
excludes = ['email', 'password', 'is_active', 'is_staff', 'is_superuser']
serializer = Serializer(formats=['json', 'jsonp'])
always_return_data = True
filtering = {
'username': 'exact',
'id': ALL_WITH_RELATIONS,
}
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/login%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('login'), name="api_login"),
url(r'^(?P<resource_name>%s)/logout%s$' %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('logout'), name='api_logout'),
]
def login(self, request, **kwargs):
self.method_check(request, allowed=['post'])
data = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
username = data.get('username', '')
password = data.get('password', '')
user = authenticate(username=username, password=password)
if user:
if user.is_active:
login(request, user)
return self.create_response(request, {
'success': True
})
else:
return self.create_response(request, {
'success': False,
'reason': 'disabled',
}, HttpForbidden )
else:
return self.create_response(request, {
'success': False,
'reason': 'incorrect user information',
}, HttpUnauthorized )
def logout(self, request, **kwargs):
self.method_check(request, allowed=['get'])
if request.user and request.user.is_authenticated():
logout(request)
return self.create_response(request, { 'success': True })
else:
return self.create_response(request, { 'success': False }, HttpUnauthorized)
lass CreateUserResource(ModelResource):
class Meta:
allowed_methods = ['post']
object_class = User
resource_name = 'register'
queryset = User.objects.all()
authentication = Authentication()
authorization = Authorization()
include_resource_uri = False
fields = ['username', 'id']
always_return_data = True
def obj_create(self, bundle, request=None, **kwargs):
username, password = bundle.data['username'], bundle.data['password']
try:
bundle.obj = User.objects.create_user(username, '', password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
front end:
photodice.controller('userController', function($scope, $resource, userFactory) {
$scope.userName = '';
$scope.userPw = '';
$scope.Signup = function() {
var userC = $resource("http://xx.xxx.xxx.xx:xxxx/api/v1/register/");
var newUser = new userC();
newUser.username = $scope.userName;
newUser.password = $scope.userPw;
newUser.$save(function (user, headers) {// Success
userFactory.setUser(user.username, user.id);
console.log("$save (signUp) success " + JSON.stringify(user));
$scope.userName = '';//cleanup
$scope.userPw = '';//cleanup
}, function (error) {// failure - TODO: add message
console.log("$save (signUp) failed " + JSON.stringify(error.data.error_message))
});
}
$scope.Signin = function() {
var userResource = $resource('http://xx.xxx.xxx.xx:xxxx/api/v1/user/login/');
user = new userResource();
user.username = $scope.userName;
user.password = $scope.userPw;
user.$save(function () {// Success
userFactory.setUser($scope.userName);
console.log("$save (signIn) success " + JSON.stringify(user));
$scope.userName = '';//cleanup
$scope.userPw = '';//cleanup
}, function (error) {// failure - TODO: add message
console.log("$save (signIn) failed " + JSON.stringify(error.data.error_message));
});
}
$scope.Signout = function() {
console.log("called signout");
var userResource = $resource('http://xx.xxx.xxx.xx:xxxx/api/v1/user/logout/:user', { user:'#user' });
var user = userResource({user:userFactory.getUser()}, function() {
user.$save(function (user, headers) {// Success
userFactory.setUser('');
console.log("$save (signIn) success " + JSON.stringify(user));
}, function (error) {// failure - TODO: add message
console.log("$save (signIn) failed " + JSON.stringify(error.data.error_message));
});
});
}
});
But I have some problems and questions:
Firstly I can sign in with the superuser account, but i only get {"success":true,"$resolved":true} as respons... should i not get some sort of token or id or more data?
Secoundly i can signup new users, but they can NOT sign in as i get: 401 (UNAUTHORIZED)
Edit: upon further investigation i notised that although I can sign up new users, thay do not get any password set... why is this?
1
But I have some problems and questions: Firstly I can sign in with the
superuser account, but i only get {"success":true,"$resolved":true} as
respons... should i not get some sort of token or id or more data?
What you do in login is assigning request with user. You authenticated user here: user = authenticate(username=username, password=password) and assigned that user to request here: login(request, user). So Django will now recognize request.user as that user during your session.
You haven't defined authentication method in your resource therefore is default. It gives access to anonymous users also so don't have to be even authenticated to have access. Once your decide which authentication you want to use then you will think about tokens and stuff.
See this: Authentication in Tastypie
2
Secoundly i can signup new users, but they can NOT sign in as i get:
401 (UNAUTHORIZED)
Your are seeing this most likely because your password or username is incorrect. user = authenticate(username=username, password=password) gives you user is None and your eles block is executed. You can make sure with printing logs in that step.
3
Edit: upon further investigation i notised that although I can sign up
new users, thay do not get any password set... why is this?
I tested the same code and works perfectly. Make sure you don't have typo on frontend side. And print logs with values in obj_create to make sure they aren't empty.
4
To allow session authentication is quite difficult and it is capable for another question. This make it possible to get request.user. (Very insecure but simple)
class PasswordAuthentication(Authentication):
def is_authenticated(self, request, **kwargs):
"""
Allow get not authenticated users but try assign user to request
if possible.
"""
try:
username, password = request.GET.get('username'), request.GET.get('password')
except ValueError:
return True
if not username or not password:
return True
try:
user = User.objects.get(username=username, password=password)
except (User.DoesNotExist, User.MultipleObjectsReturned):
return True
if not self.check_active(user):
return True
request.user = user
return True
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
excludes = ['email', 'password', 'is_active', 'is_staff', 'is_superuser']
serializer = Serializer(formats=['json', 'jsonp'])
authentication = PasswordAuthentication()
always_return_data = True
filtering = {
'username': 'exact',
'id': ALL_WITH_RELATIONS,
}
[...]
def logout(self, request, **kwargs):
self.method_check(request, allowed=['get'])
if request.user and request.user.is_authenticated():
logout(request)
return self.create_response(request, { 'success': True })
else:
return self.create_response(request, { 'success': False }, HttpUnauthorized)
call backend with http://xx.xxx.xxx.xx:xxxx/api/v1/user/logout/4/?username=asdf&password=1234