![]() |
VOOZH | about |
We’re so glad you’re here. You can expect all the best TNS content to arrive Monday through Friday to keep you on top of the news and at the top of your game.
Check your inbox for a confirmation email where you can adjust your preferences and even join additional groups.
Follow TNS on your favorite social media networks.
Become a TNS follower on LinkedIn.
Check out the latest featured and trending stories while you wait for your first TNS newsletter.
u2f.start_authentication() and u2f.finish_authentication() in the backend, and u2f.sign() in the frontend.
Let’s start with u2f.start_authentication(), which takes in the browser’s application ID and the currently registered devices.
The U2F API authentication process starts with the backend generating a challenge, an example of which is shown below:
{
"appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
"challenge": "VwmGI-4…",
"registeredKeys": [
{
"appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
"keyHandle": "cxSl4oQ…",
"publicKey": "BP4Q8MR…",
"transports": [
"usb"
],
"version": "U2F_V2"
}
]
}
u2f.sign() takes the challenge as an input and returns a promise that is the result of:
{
"keyHandle": "cxSl4oQ…",
"clientData": "eyJ0eXA…",
"signatureData": "AQAAAQ4…"
}
U2f.complete_authentication() with the following two parameters: the original challenge data and the newly generated client data object passed in. This method will verify the device with the parameters and return the device info if it succeeded. From there, the server can allow the user to pass through the 2FA process.
u2f.start_authentication() with its counterpart. The data types that the WebAuthn API takes are not quite the same ones used in U2F API. In fact, one of the main pain points was converting the necessary fields into the correct data type.
We want to authenticate users on legacy U2F API and WebAuthn, so we will create an authentication server first. The following will create an authentication server using WebAuthn that is backward compatible with U2F API:
webauthn_authentication_server = U2FFido2Server(
app_id=u2f_app_id,
rp={
"id": “sentry-webauthn.io”,
"name": "Sentry with WebAuthn"}
)
app_id will be the same value as before. The rp, or Relying Party, is an object that contains an ID, which is the hostname of the URL, and the name of your Relying Party.
Next, we need to generate a list of credentials, which is the same as the list of devices for U2F API. Keep in mind that the list of credentials will contain both WebAuthn and U2F API registered devices and that list needs to be manipulated.
credentials = [] for device in self.get_u2f_devices(): if type(device) == AuthenticatorData: credentials.append(device.credential_data) else: credentials.append(create_credential_object(device))
def create_credential_object(registeredKey): return base.AttestedCredentialData.from_ctap1( websafe_decode(registeredKey["keyHandle"]), websafe_decode(registeredKey["publicKey"]), )
register_begin() on the WebAuthn server that we created earlier, with credentials as its parameter. This will return a challenge and state.
The challenge is needed for the browser to perform authentication, but we will only use the PublicKey object within the challenge. In addition, you should store the state in your sessions, as it will be needed later.
challenge, state = self.webauthn_authentication_server.authenticate_begin( credentials=credentials ) request.session["webauthn_authentication_state"] = state return ActivationChallengeResult( challenge=cbor.encode(challenge["publicKey"]) )
u2f.sign(), we can call its WebAuthn equivalent navigator.credentials.get() with the challenge data. This library is now native to modern browsers, so don’t worry about importing any libraries.
const challengeArray = base64urlToBuffer(
challengeData.webAuthnAuthenticationData
);
const challenge = cbor.decodeFirst(challengeArray);
challenge.then(data => {
webAuthnSignIn(data);
}).catch(err => {
const failure = 'DEVICE_ERROR';
Sentry.captureException(err);
this.setState({
deviceFailure: failure,
hasBeenTapped: false,
});
});
function webAuthnSignIn(publicKeyCredentialRequestOptions) {
return navigator.credentials.get({
publicKey: publicKeyCredentialRequestOptions,
}).then(data => {
// Send to backend
})
}
navigator.credentials.get(), we need to send the appropriate data to the backend to finish authentication. To convert the PublicKeyCredential that was obtained from navigator.credentials.get(), we can run it through the following function:
getU2FResponse(data) {
if (data.response) {
const authenticatorData = {
keyHandle: data.id,
clientData: bufferToBase64url(data.response.clientDataJSON),
signatureData: bufferToBase64url(data.response.signature),
authenticatorData: bufferToBase64url(data.response.authenticatorData),
};
return JSON.stringify(authenticatorData);
}
return JSON.stringify(data);
}
authenticate_complete on the authentication server that was made earlier with the following parameters:
fido2.client.ClientDatafido2.ctap2.authenticatorDataself.webauthn_authentication_server.authenticate_complete( state=request.session["webauthn_authentication_state"], credentials=credentials, credential_id=websafe_decode(response["keyHandle"]), client_data=ClientData(websafe_decode(response["clientData"])), auth_data=AuthenticatorData(websafe_decode(response["authenticatorData"])), signature=websafe_decode(response["signatureData"]), )
u2f.begin_registration() and u2f.complete_registration() in the backend, and u2f.register() in the frontend. Once again, we will start with u2f.begin_registration(). This API call takes in the u2f application ID and list of registered devices. This results in the following data being sent to the browser to begin the registration process:
{
"appId": "https://your-webauthn-app/2fa/u2fappid.jso",
"registerRequests": [
{
"challenge": "uexgFSl…",
"version": "U2F_V2"
}
],
"registeredKeys": []
}
u2f.sign(), u2f.register() will take the previously generated results and return a promise that will look like the following if the device is registrable:
{
"registrationData": "BQQ1xlC…",
"version": "U2F_V2",
"challenge": "Jkh_Tfo…",
"appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
"clientData": "eyJ0eXA…"
}
{
"appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
"keyHandle": "SnllNGC…",
"publicKey": "BIs-gsW…",
"transports": [
"usb"
],
"version": "U2F_V2"
}
u2f.begin_registration()with its counterpart. We need to create a FIDO2Server and import it from fido2.server. We don’t need to make it backward compatible, as all new devices will be registered with WebAuthn.
from fido2.server import Fido2Server from fido2.webauthn import PublicKeyCredentialRpEntity rp = PublicKeyCredentialRpEntity(rp_id, "Sentry") webauthn_registration_server = Fido2Server(rp)
register_begin() with:
user: dictionary with the user’s id, name, and display name
credentials: the list we just generated
user_verification: normally defaulted to discouraged
You should get a result similar to this:
{
"publicKey": {
"authenticatorSelection": {
"userVerification": <UserVerificationRequirement.DISCOURAGED: "discouraged">},
"challenge": b"\xe9)#\x86\xfa.\xa9\x82r\x86\xf7\x15e\xb5m\xdc"
b"\x1dR\xc4\x1b\xdb\xab\x94\x88\xb8\x94\xf43"
b"b\x03\xab\n",
"excludeCredentials": [],
"pubKeyCredParams": [
{"alg": -7,
"type": <PublicKeyCredentialType.PUBLIC_KEY: "public-key">},
{"alg": -8,
"type": <PublicKeyCredentialType.PUBLIC_KEY: "public-key">},
{"alg": -37,
"type": <PublicKeyCredentialType.PUBLIC_KEY: "public-key">},
{"alg": -257,
"type": <PublicKeyCredentialType.PUBLIC_KEY: "public-key">}],
"rp": {"id": "<$YOUR_APP>",
"name": "Sentry"},
"user": {"displayName": "<$YOUR_NAME>",
"id": b"\x00",
"name": "<$YOUR_APP>"
}
}
}
cbor.encode() method and base64 encode that to a string.
publicKeyCredentialCreate = cbor.encode(registration_data) return b64encode(publicKeyCredentialCreate)
navigator.credentials.create():
challenge, state = self.webauthn_authentication_server.authenticate_begin(
credentials=credentials
)
request.session["webauthn_authentication_state"] = state
return ActivationChallengeResult(challenge=cbor.encode(challenge["publicKey"]))
webAuthnRegister(publicKey) {
const promise = navigator.credentials.create({publicKey});
this.submitU2fResponse(promise);
}
navigator.credentials.create() is resolved, we need to run it through getU2FResponse() before returning it to the server again.
navigator.credentials.create(). The following are needed for register_complete():
data = json.loads(response_data) client_data = ClientData( websafe_decode(data["response"]["clientDataJSON"]) ) att_obj = base.AttestationObject( websafe_decode(data["response"]["attestationObject"]) ) binding = webauthn_registration_server.register_complete( state, client_data, att_obj )
{
"type": "webauthn.create",
"challenge": "_Uas89Y…",
"origin": "https://<$YOUR_APP>",
"crossOrigin": false
}
AttestationObject(
fmt: 'none',
auth_data: AuthenticatorData(
rp_id_hash: h'74cb1ce…5',
flags: 0x41,
counter: 281,
credential_data: AttestedCredentialData(
aaguid: h'0000000…',
credential_id: h'63af2c9…',
public_key: {...}
),
att_statement: {},
ep_attr: None,
large_blob_key: None
)
)
AuthenticatorData(
rp_id_hash: h'74cb1ce…',
flags: 0x41,
counter: 281,
credential_data: AttestedCredentialData(
aaguid: h'0000000…',
credential_id: h'63af2c9…',
public_key: {...}
)
)