I'm trying to implement U2F authentication devices in my django app. The primary issue now is, all of my front-end registration calls fail.
I'm using the u2f-api.js
script and the python-u2flib-server
python script to implement this.
I am following this pattern:
1) Generate a key/challenge with the u2flib
from u2flib_server import u2f
app_id = 'https://127.0.0.1'
reg = u2f.begin_registration(app_id)
print reg
# {'registeredKeys': [],
'registerRequests': [{'challenge': u'pLzGmABMwBzQkco6INeFNuPsAG6KhgfVeYFeV0QBf1g', version': 'U2F_V2'}],
'appId': 'https://127.0.0.1'}
2) Register the key from browser
var reg_data = {'registeredKeys': [], 'registerRequests': [{'challenge': 'pLzGmABMwBzQkco6INeFNuPsAG6KhgfVeYFeV0QBf1g', 'version': 'U2F_V2'}], 'appId': 'https://127.0.0.1'}
u2f.register(reg_data['appId'], reg_data['registerRequests'], [], function(resp) { console.log(resp) });
This consistently returns a {errorCode: 2}
response, which essentially means bad request. However, I'm unable to determine what part of this request is invalid.
I am using runserver_plus --cert certname
to run my local webserver so the site is served via HTTPS. I've also tried using NGROK to access my site over HTTPS and attempted the same code. I consistently get the same response.
I would appreciate any help, pointers or guidance on my implementation as I've been struggling for a few days and the existing documentation around U2F libraries and implementations is pretty thin.
-- Update --
I've actually made slight progress here. I am able to receive the generate the key response. However, I am still unable to register this key with the python library.
I end up having something like this on the server side:
response = {'challenge': 'okGbjnbE2V9cT42X2wm-PA9pm7k3KpTETVEv2SqEUxE', 'registrationData': 'BRS5y7dFXs0O60o2cUFc-SZtKG3jibpFQGuwQDyTQkSWeQUNWn\\u2026bEwIhALYcM1NospvymAbv83lTlpLjaa2ICSFQv-5RYfzkPCc9', 'version': 'U2F_V2', 'clientData': 'eyJjaGFsbGVuZ2UiOiJva0dianBnRTJWOWNUNDJYMndtLVBBOX\\u2026R5cCI6Im5hdmlnYXRvci5pZC5maW5pc2hFbnJvbGxtZW50In0'}
register_request = {'registeredKeys': [], 'registerRequests': [{'challenge': 'okGbjpgE2V9cD32X2wm-PA9pm7k3KpKN5VEv2SqEUxE', 'version': 'U2F_V2'}], 'appId': 'https://127.0.0.1:8000'}
u2f.complete_registration(register_request, response)
However, this results in the following error:
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/usr/local/lib/python2.7/dist-packages/u2flib_server/u2f.py", line 45, in complete_registration
return U2fRegisterRequest.wrap(request).complete(response, valid_facets)
File "/usr/local/lib/python2.7/dist-packages/u2flib_server/model.py", line 419, in complete
_validate_client_data(resp.clientData, req.challenge, Type.REGISTER,
File "/usr/local/lib/python2.7/dist-packages/u2flib_server/model.py", line 339, in clientData
return ClientData.wrap(self['clientData'])
File "/usr/local/lib/python2.7/dist-packages/u2flib_server/model.py", line 261, in wrap
return data if isinstance(data, cls) else cls(data)
File "/usr/local/lib/python2.7/dist-packages/u2flib_server/model.py", line 328, in __init__
super(ClientData, self).__init__(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/u2flib_server/model.py", line 239, in __init__
kwargs = json.loads(arg.decode('utf-8'))
File "/usr/lib/python2.7/json/__init__.py", line 339, in loads
return _default_decoder.decode(s)
File "/usr/lib/python2.7/json/decoder.py", line 364, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python2.7/json/decoder.py", line 382, in raw_decode
raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded
I'm stuck again! I've tried creating a json dumps out of the values before passing them to u2f.register
to no avail- I get the exact same error.
After a lot of research, digging up random blog posts and a lot of trial and error, I've finally been able to complete this task with the above mentioned libraries. I will post here an example of how I have achieved this. First, ensure you are running your local webserver over https at https://localhost:8000
(or whatever port- but localhost
is needed).
First, generate a challenge from the python library
def get(self, request, *args, **kwargs):
user_devices = [..get user device JSON blobs if they exist, or just blank]
register_request = u2f.begin_registration('https://127.0.0.1:8000', user_devices)
# return value to front end
return render(request, self.template_name, {
'register_request': register_request
})
In the front end, signal browser to begin U2F registration. This will emit a signal to the key to ready itself. Post the result from the registration to your register completion endpoint
registerRequest = {{ register_request | safe }} //json result of above
var regReq = {'challenge':registerRequest.challenge, 'version': registerRequest.version}
window.u2f.register(this.appID, [regReq], [], function(keyAuthResponse) {
$.ajax({url: '/path-to-registration/',
type: "POST",
data: keyAuthResponse,
success: function(res){ console.log('sent verification' }
})
})
On your server side, accept and verify the key
data = reg{'challenge': request.POST.get('challenge'),
'clientData': request.POST.get('clientData'),
'registrationData': request.POST.get('registrationData'),
'version': request.POST.get('version'),
})
#You need hte original register request, either in session or elsewhere to fetch
register_request = request.session.get('_u2f_registration_request')
device_details, facets = u2f.complete_registration(register_request, data)
The complete_registration
will return no errors if the device registered.
I hope this serves to help someone in the future.