I made the following API using Django REST Framework, so make a note.
--Login --User created --Get user information --User information update (including password update) --User deletion
Even though I made it, the part I made with a relatively large atmosphere is large, and there is also the problem of whether this is optimal, so it is for reference only.
Regarding the model, you can use the default user model, but if you want to create an actual application, you will need to customize something, so I created the following model
user/models.py
from django.db import models
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser, _user_has_perm
)
from django.core import validators
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
class AccountManager(BaseUserManager):
def create_user(self, request_data, **kwargs):
now = timezone.now()
if not request_data['email']:
raise ValueError('Users must have an email address.')
profile = ""
if request_data.get('profile'):
profile = request_data['profile']
user = self.model(
username=request_data['username'],
email=self.normalize_email(request_data['email']),
is_active=True,
last_login=now,
date_joined=now,
profile=profile
)
user.set_password(request_data['password'])
user.save(using=self._db)
return user
def create_superuser(self, username, email, password, **extra_fields):
request_data = {
'username': username,
'email': email,
'password': password
}
user = self.create_user(request_data)
user.is_active = True
user.is_staff = True
user.is_admin = True
user.save(using=self._db)
return user
class Account(AbstractBaseUser):
username = models.CharField(_('username'), max_length=30, unique=True)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
profile = models.CharField(_('profile'), max_length=255, blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = AccountManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
def user_has_perm(user, perm, obj):
return _user_has_perm(user, perm, obj)
def has_perm(self, perm, obj=None):
return _user_has_perm(self, perm, obj=obj)
def has_module_perms(self, app_label):
return self.is_admin
def get_short_name(self):
return self.first_name
@property
def is_superuser(self):
return self.is_admin
class Meta:
db_table = 'api_user'
swappable = 'AUTH_USER_MODEL'
You can create the admin user as usual with $ python manage.py createsuperuser
.
The following two points are different from the default.
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
profile = models.CharField(_('profile'), max_length=255, blank=True)
that's all.
This time, we implemented token authentication by JWT. Install django-rest-framework-jwt with pip.
$ pip install djangorestframework-jwt
After installation, add the following to settings.py.
djangoproject/settings.py
JWT_AUTH = {
'JWT_VERIFY_EXPIRATION': False,
'JWT_AUTH_HEADER_PREFIX': 'JWT',
}
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
'NON_FIELD_ERRORS_KEY': 'detail',
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}
If you set 'JWT_VERIFY_EXPIRATION': False,
, the token expiration date will be permanent.
Next, go to url.py as follows
djangoproject/urls.py
from django.conf.urls import url
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
url(r'^login/', obtain_jwt_token),
]
With this alone, you can get a token by POSTing to / login using email and password.
First of all, we need an API endpoint, so create urls.py in the application and
user/urls.py
from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister
urlpatterns = [
url(r'^register/$', AuthRegister.as_view()),
]
Load it into urls.py on the project side.
djangoproject/urls.py
from django.conf.urls import include, url
from django.contrib import admin
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
url(r'^login/', obtain_jwt_token),
url(r'^api/', include('authentication.urls')),
]
POST to / api / register / to allow user creation
user/views.py
from django.contrib.auth import authenticate
from django.db import transaction
from django.http import HttpResponse, Http404
from rest_framework import authentication, permissions, generics
from rest_framework_jwt.settings import api_settings
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.response import Response
from rest_framework import status, viewsets, filters
from rest_framework.views import APIView
from .serializer import AccountSerializer
from .models import Account, AccountManager
#User-created View(POST)
class AuthRegister(generics.CreateAPIView):
permission_classes = (permissions.AllowAny,)
queryset = Account.objects.all()
serializer_class = AccountSerializer
@transaction.atomic
def post(self, request, format=None):
serializer = AccountSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
By using generics, you can limit the HTTP methods that the class can accept. Check here for more information on generics (http://www.django-rest-framework.org/api-guide/generic-views/). CreateAPIView is used for create-only endpoints.
Also, as a matter of course, for user creation, it is necessary to POST to the endpoint even if it is not authenticated, so permission_classes = (permissions.AllowAny,)
is set.
from django.contrib.auth import update_session_auth_hash
from rest_framework import serializers
from .models import Account, AccountManager
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = ('id', 'username', 'email', 'profile', 'password')
def create(self, validated_data):
return Account.objects.create_user(request_data=validated_data)
Roughly speaking, it feels like calling a user-created method defined in Model.
For the time being, if you POST the json of email, password, profile (optional) to / api / register / in the above state, the user will be created.
Regarding the acquisition of user information, it depends on the application, but for the time being, the logged-in user can only acquire his own information. If you want to get information about other users, you need to create another view and endpoint, so don't worry.
Edit urls.py on the application side.
user/urls.py
from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister, AuthInfoGetView
urlpatterns = [
url(r'^register/$', AuthRegister.as_view()),
url(r'^mypage/$', AuthInfoGetView.as_view()),
]
Get user information when throwing GET to / api / mypage.
Add the following to views.py earlier
user/views.py
#View of user information acquisition(GET)
class AuthInfoGetView(generics.RetrieveAPIView):
permission_classes = (permissions.IsAuthenticated,)
queryset = Account.objects.all()
serializer_class = AccountSerializer
def get(self, request, format=None):
return Response(data={
'username': request.user.username,
'email': request.user.email,
'profile': request.user.profile,
},
status=status.HTTP_200_OK)
RetrieveAPIView is an endpoint dedicated to the GET method.
Also, since I am trying to get my own information, I am trying to get it only when I am logged in with permission_classes = (permissions.IsAuthenticated,)
.
In this state, add {'Content-Type':'application / json','Authorization':'JWT [token obtained at login]'}
to the header and throw the GET method to log in. You can get the username / email / profile of the user you are.
There is a talk about whether to use PUT or PATCH when updating information, but for the time being, I used PUT this time, but now that I think about it, I think that PATCH is good. However, since I made it, I will talk about it in PUT.
Create an endpoint called / auth_update and call the view AuthInfoUpdateView.
user/urls.py
from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister, AuthInfoGetView, AuthInfoUpdateView
urlpatterns = [
url(r'^register/$', AuthRegister.as_view()),
url(r'^mypage/$', AuthInfoGetView.as_view()),
url(r'^auth_update/$', AuthInfoUpdateView.as_view()),
]
Add the following to views.py. Implementation of AuthInfoUpdateView, a view that is called when there is a PUT in / auth_update. Since email is used as the key, it is necessary to include the email of the logged-in user in JSON when sending PUT.
user/views.py
#View of user information update(PUT)
class AuthInfoUpdateView(generics.UpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = AccountSerializer
lookup_field = 'email'
queryset = Account.objects.all()
def get_object(self):
try:
instance = self.queryset.get(email=self.request.user)
return instance
except Account.DoesNotExist:
raise Http404
As the name suggests, UpdateAPIView accepts PUT / PATCH as an update-only view.
Edit AccountSerializer in serializers.py as follows.
user/serializers.py
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
class Meta:
model = Account
fields = ('id', 'username', 'email', 'profile', 'password')
def create(self, validated_data):
return Account.objects.create_user(request_data=validated_data)
def update(self, instance, validated_data):
if 'password' in validated_data:
instance.set_password(validated_data['password'])
else:
instance = super().update(instance, validated_data)
instance.save()
return instance
If the password is updated, it is necessary to hash the received password, so use the set_password method.
As with getting user information, you need to PUT with {'Content-Type':'application / json','Authorization':'JWT [token obtained at login]'}
in the header.
Finally, we will implement the deletion of the logged-in user.
I create an endpoint called / delete and call AuthInfoDeleteView.
user/urls.py
from django.conf.urls import include, url
from rest_framework import routers
from .views import AuthRegister, AuthInfoGetView, AuthInfoUpdateView, AuthInfoDeleteView
urlpatterns = [
url(r'^register/$', AuthRegister.as_view()),
url(r'^mypage/$', AuthInfoGetView.as_view()),
url(r'^auth_update/$', AuthInfoUpdateView.as_view()),
url(r'^delete/$', AuthInfoDeleteView.as_view()),
]
An implementation of AuthInfoDeleteView.
user/views.py
#User Delete View(DELETE)
class AuthInfoDeleteView(generics.DestroyAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = AccountSerializer
lookup_field = 'email'
queryset = Account.objects.all()
def get_object(self):
try:
instance = self.queryset.get(email=self.request.user)
return instance
except Account.DoesNotExist:
raise Http404
By setting generics.DestroyAPIView
, it will be a view that accepts only the DELETE method.
By sending DELETE with {'Content-Type':'application / json','Authorization':'JWT [token obtained at login]'}
in the header as well as getting and updating user information You can delete the logged-in user.
that's all.
Actually, email cannot be updated in the current state. No, I can do it, but since the token I have on the client side is the one logged in with the old email, the behavior will be strange if the client side does not have the token obtained with the new email. You can issue a new token with a new email by POSTing to / login with a password and a new email, but that's nonsense. You need to have the password in clear text on the client side. For the time being, I would like to investigate this in a little more detail.
Also, I think it is necessary to prepare an API for reissuing passwords in order to forget the password, but that was annoying </ s>, so I decided not to do it this time.
Also, the obvious problem is that if the login token is obtained in some way, the user information can be easily tampered with or deleted. However, I think the problem of maliciously robbing tokens is a matter of JWT. This time the token expiration date is infinite, but I wonder if it is necessary to set an expiration date and issue a refresh token when sending a request to the API when actually publishing the application. I think.
In addition, the one that combines the above API and Angular on the front is in the following repository. https://github.com/xKxAxKx/auth_django_and_angular2
Recommended Posts