Skip to content

Instantly share code, notes, and snippets.

@ryu22e
Last active December 10, 2018 12:49
Show Gist options
  • Save ryu22e/f5be471c8bb36192c29efdb3688a9ec5 to your computer and use it in GitHub Desktop.
Save ryu22e/f5be471c8bb36192c29efdb3688a9ec5 to your computer and use it in GitHub Desktop.
WebAuthnサインイン(サーバーサイド)
"""users/backends.py"""
from django.conf import settings
from fido2.server import Fido2Server, RelyingParty
from .models import User, WebAuthnPublicKey
rp = RelyingParty(settings.RELYING_PARTY_DOMAIN, settings.RELYING_PARTY_NAME)
server = Fido2Server(rp)
class WebAuthnBackend:
def authenticate(self, request, username, credential_id, challenge, client_data, auth_data, signature):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
credentials = (
WebAuthnPublicKey.
objects.
credentials(username)
)
try:
server.authenticate_complete(
credentials,
credential_id,
challenge,
client_data,
auth_data,
signature
)
except ValueError:
return None
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
"""users/serializers.py"""
from rest_framework import serializers
class AuthenticateBeginSerializer(serializers.Serializer):
username = serializers.CharField(max_length=150)
"""users/urls.py"""
from django.urls import include, path
from . import views
app_name = 'users'
urlpatterns = [
path('signin/', views.SignInView.as_view(), name='signin'),
path(
'apis/',
include(
[
path('authenticate-begin/', views.AuthenticateBeginViewSet.as_view(),
name='authenticat_begin'),
path('authenticate-complete/', views.AuthenticateCompleteViewSet.as_view(),
name='authenticat_complete'),
],
),
),
]
"""users/views.py"""
from django.conf import settings
from django.contrib.auth import authenticate, login
from django.urls import reverse
from django.views import generic
from fido2.client import ClientData
from fido2.ctap2 import AuthenticatorData
from fido2.server import Fido2Server, RelyingParty
from rest_framework.response import Response
from rest_framework.views import APIView
from .forms import SignInForm
from .models import WebAuthnPublicKey
from .parsers import CBORParser
from .permissions import CSRFPermission
from .renderers import CBORRenderer
from .serializers import AuthenticateBeginSerializer
rp = RelyingParty(settings.RELYING_PARTY_DOMAIN, settings.RELYING_PARTY_NAME)
server = Fido2Server(rp)
class SignInView(generic.TemplateView):
template_name = 'users/signin.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['form'] = SignInForm()
return context
class AuthenticateBeginViewSet(APIView):
serializer_class = AuthenticateBeginSerializer
renderer_classes = (CBORRenderer,)
permission_classes = (CSRFPermission,)
def post(self, request, format=None):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
username = serializer.data['username']
credentials = (
WebAuthnPublicKey.
objects.
credentials(username)
)
auth_data = server.authenticate_begin(credentials)
request.session['challenge'] = auth_data['publicKey']['challenge']
request.session['username'] = username
return Response(auth_data)
class AuthenticateCompleteViewSet(APIView):
parser_classes = (CBORParser,)
permission_classes = (CSRFPermission,)
def post(self, request, format=None):
data = request.data[0]
credential_id = data['credentialId']
client_data = ClientData(data['clientDataJSON'])
auth_data = AuthenticatorData(data['authenticatorData'])
signature = data['signature']
user = authenticate(
username=request.session.pop('username'),
credential_id=credential_id,
challenge=request.session.pop('challenge'),
client_data=client_data,
auth_data=auth_data,
signature=signature,
)
if user and user.is_active:
login(request, user)
return Response(
{
'status': 'OK',
'redirect_to': reverse('accounts:profile'), # ログイン必須のページ
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment