-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rest framework integration #136
Comments
I managed to create some work in progress for the auth request and auth api, borrowing code from the views.py import base64
from http.client import BAD_REQUEST
from typing import Tuple, Dict
from django.contrib.auth import get_user_model, authenticate, login
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.encoding import force_text
from django_fido.constants import AUTHENTICATION_USER_SESSION_KEY
from django_fido.views import Fido2ViewMixin, Fido2ServerError, Fido2Error
from django.utils.translation import gettext_lazy as _
from fido2.client import ClientData
from fido2.ctap2 import AuthenticatorData
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.exceptions import ValidationError
from rest_framework import serializers
class FidoAuthenticationSerializer(serializers.Serializer):
client_data = serializers.CharField()
credential_id = serializers.CharField()
authenticator_data = serializers.CharField()
signature = serializers.CharField()
def validate_client_data(self, value) -> ClientData:
"""Return decoded client data."""
try:
return ClientData(base64.b64decode(value))
except ValueError:
raise ValidationError(_('FIDO 2 response is malformed.'), code='invalid')
def validate_credential_id(self, value) -> bytes:
"""Return decoded credential ID."""
try:
return base64.b64decode(value)
except ValueError:
raise ValidationError(_('FIDO 2 response is malformed.'), code='invalid')
def validate_authenticator_data(self, value) -> AuthenticatorData:
"""Return decoded authenticator data."""
try:
return AuthenticatorData(base64.b64decode(value))
except ValueError:
raise ValidationError(_('FIDO 2 response is malformed.'), code='invalid')
def validate_signature(self, value) -> bytes:
"""Return decoded signature."""
try:
return base64.b64decode(value)
except ValueError:
raise ValidationError(_('FIDO 2 response is malformed.'), code='invalid')
class GetUserMixin(object):
def get_user(self):
# borrowed from django-fido Fido2AuthenticationViewMixin
user_pk = self.request.session.get(AUTHENTICATION_USER_SESSION_KEY)
return get_user_model().objects.get(pk=user_pk)
class TwoStepAuthRequestView(GetUserMixin, Fido2ViewMixin, APIView):
authentication_classes = []
permission_classes = []
def create_fido2_request(self) -> Tuple[Dict, Dict]:
"""Create and return FIDO 2 authentication request.
@raise ValueError: If request can't be created.
"""
user = self.get_user()
assert user and user.is_authenticated, "User must not be anonymous for FIDO 2 requests."
credentials = self.get_credentials(user)
if not credentials:
raise Fido2Error("Can't create FIDO 2 authentication request, no authenticators found.",
error_code=Fido2ServerError.NO_AUTHENTICATORS)
return self.server.authenticate_begin(credentials, user_verification=self.user_verification)
def get(self, request: Request) -> Response:
"""Return JSON with FIDO 2 request."""
try:
request_data, state = self.create_fido2_request()
except ValueError as error:
return Response({
'error_code': getattr(error, 'error_code', Fido2ServerError.DEFAULT),
'message': force_text(error),
'error': force_text(error), # error key is deprecated and will be removed in the future
}, status=BAD_REQUEST)
# Encode challenge into base64 encoding
challenge = request_data['publicKey']['challenge']
challenge = base64.b64encode(challenge).decode('utf-8')
request_data['publicKey']['challenge'] = challenge
# Encode credential IDs, if exists - registration
if 'excludeCredentials' in request_data['publicKey']:
encoded_credentials = []
for credential in request_data['publicKey']['excludeCredentials']:
encoded_credential = credential.copy()
encoded_credential['id'] = base64.b64encode(encoded_credential['id']).decode('utf-8')
encoded_credentials.append(encoded_credential)
request_data['publicKey']['excludeCredentials'] = encoded_credentials
# Encode credential IDs, if exists - authentication
if 'allowCredentials' in request_data['publicKey']:
encoded_credentials = []
for credential in request_data['publicKey']['allowCredentials']:
encoded_credential = credential.copy()
encoded_credential['id'] = base64.b64encode(encoded_credential['id']).decode('utf-8')
encoded_credentials.append(encoded_credential)
request_data['publicKey']['allowCredentials'] = encoded_credentials
# Store the state into session
self.request.session[self.session_key] = state
return Response(request_data)
class TwoStepAuthView(GetUserMixin, Fido2ViewMixin, APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializer = FidoAuthenticationSerializer(data=request.data)
serializer.is_valid()
self.complete_authentication(serializer.validated_data)
user = self.get_user()
login(self.request, form.get_user())
return Response()
def complete_authentication(self, data) -> AbstractBaseUser:
"""
Complete the authentication.
@raise ValidationError: If the authentication can't be completed.
"""
state = self.request.session.pop(self.session_key, None)
if state is None:
raise ValidationError(_('Authentication request not found.'), code='missing')
fido_kwargs = dict(
fido2_server=self.server,
fido2_state=state,
fido2_response=data,
)
user = authenticate(request=self.request, user=self.get_user(), **fido_kwargs)
if user is None:
raise ValidationError(_('Authentication failed.'), code='invalid')
return user |
And the js part for auth process // /js/api/auth.js
export default {
....
fidoTwoStepAuthRequest(){
return client.get('auth/fido/auth-request/')
},
fidoTwoStepAuthenticate(data){
return client.post('auth/fido/authenticate/', data)
},
} import React from 'react';
import {Button} from 'react-bootstrap';
import AuthAPI from '@/js/api/auth';
const FidoForm = ({onSuccess}) => {
const base64ToArrayBuffer = (base64) => {
const binaryString = window.atob(base64);
const bytes = new Uint8Array(binaryString.length)
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i)
}
return bytes
}
const arrayBufferToBase64 = (buffer) => {
let binary = ''
const bytes = new Uint8Array(buffer)
for (const byte of bytes)
binary += String.fromCharCode(byte)
return window.btoa(binary)
}
const onFidoSubmit = (formData) => {
AuthAPI.fidoTwoStepAuthRequest().then(
data => {
const publicKey = data.publicKey;
publicKey.challenge = base64ToArrayBuffer(publicKey.challenge)
// Decode credentials
const decodedCredentials = []
for (const credential of publicKey.allowCredentials){
credential.id = base64ToArrayBuffer(credential.id)
decodedCredentials.push(credential)
}
publicKey.allowCredentials = decodedCredentials;
navigator.credentials.get({ publicKey }).then(result => {
const authData = {
client_data: arrayBufferToBase64(result.response.clientDataJSON),
credential_id: arrayBufferToBase64(result.rawId),
authenticator_data: arrayBufferToBase64(result.response.authenticatorData),
signature: arrayBufferToBase64(result.response.signature)
}
AuthAPI.fidoTwoStepAuthenticate(authData).then(resp=>onSuccess(resp.token));
});
}
);
}
return (
<div>
<Button onClick={onFidoSubmit}>Login with YUBI key</Button>
</div>
);
};
export default FidoForm; |
Wow, you are a God. This code has already helped me immensely. Any idea why I get this error below when I try your For instance, if I should just use the |
The |
This looks to be out of the scope of this library. I would suggest a separate library for this functionality. |
@variable django-trench integrates with django REST framework and quote "Comes out of a box with email, SMS, mobile apps and YubiKey support." Though I don't know if they've implemented the FIDO2 WebAuthn spec yet. |
It seems like to utilize yubicloud, I don't have experience using that
…On Fri, 5 Nov 2021, 6:58 pm Dirk Haupt, ***@***.***> wrote:
@variable <https://github.com/variable> django-trench
<https://github.com/merixstudio/django-trench> integrates with django
REST framework and quote "Comes out of a box with email, SMS, mobile apps
and YubiKey support." Though I don't know if they've implemented the FIDO2
WebAuthn spec yet.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#136 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAEIIX2PO6TMK5YHDZCVMY3UKN6BPANCNFSM5HFJ7HPA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
@tpazderka I've gone through each Authentication package listed in Django Packages. None of them support Webauthn using django's REST framework. My impression is that the vast majority of production Django applications have/are moved/moving to using Django only as a backend and using something else on the frontend, so seems there would be high expected value to support REST framework integration eventually. My 2 cents. |
Hello, thanks for the package, it works great with django views.
Now I need to integrate it with our single page app, which the login is via API, is there a chance for you to include a guide or restframework integration code so I can use in my project?
The text was updated successfully, but these errors were encountered: