Fix bugs with WebAuthn preventing sign in and registration. (#22651)
This PR fixes two bugs with Webauthn support: * There was a longstanding bug within webauthn due to the backend using URLEncodedBase64 but the javascript using decoding using plain base64. This causes intermittent issues with users reporting decoding errors. * Following the recent upgrade to webauthn there was a change in the way the library expects RPOrigins to be configured. This leads to the Relying Party Origin not being configured and prevents registration. Fix #22507 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
2871ea0809
commit
19d5b2f922
3 changed files with 25 additions and 18 deletions
|
@ -28,7 +28,7 @@ func Init() {
|
||||||
Config: &webauthn.Config{
|
Config: &webauthn.Config{
|
||||||
RPDisplayName: setting.AppName,
|
RPDisplayName: setting.AppName,
|
||||||
RPID: setting.Domain,
|
RPID: setting.Domain,
|
||||||
RPOrigin: appURL,
|
RPOrigins: []string{appURL},
|
||||||
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
||||||
UserVerification: "discouraged",
|
UserVerification: "discouraged",
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,11 +15,11 @@ func TestInit(t *testing.T) {
|
||||||
setting.Domain = "domain"
|
setting.Domain = "domain"
|
||||||
setting.AppName = "AppName"
|
setting.AppName = "AppName"
|
||||||
setting.AppURL = "https://domain/"
|
setting.AppURL = "https://domain/"
|
||||||
rpOrigin := "https://domain"
|
rpOrigin := []string{"https://domain"}
|
||||||
|
|
||||||
Init()
|
Init()
|
||||||
|
|
||||||
assert.Equal(t, setting.Domain, WebAuthn.Config.RPID)
|
assert.Equal(t, setting.Domain, WebAuthn.Config.RPID)
|
||||||
assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName)
|
assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName)
|
||||||
assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigin)
|
assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigins)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@ export function initUserAuthWebAuthn() {
|
||||||
|
|
||||||
$.getJSON(`${appSubUrl}/user/webauthn/assertion`, {})
|
$.getJSON(`${appSubUrl}/user/webauthn/assertion`, {})
|
||||||
.done((makeAssertionOptions) => {
|
.done((makeAssertionOptions) => {
|
||||||
makeAssertionOptions.publicKey.challenge = decode(makeAssertionOptions.publicKey.challenge);
|
makeAssertionOptions.publicKey.challenge = decodeURLEncodedBase64(makeAssertionOptions.publicKey.challenge);
|
||||||
for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) {
|
for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) {
|
||||||
makeAssertionOptions.publicKey.allowCredentials[i].id = decode(makeAssertionOptions.publicKey.allowCredentials[i].id);
|
makeAssertionOptions.publicKey.allowCredentials[i].id = decodeURLEncodedBase64(makeAssertionOptions.publicKey.allowCredentials[i].id);
|
||||||
}
|
}
|
||||||
navigator.credentials.get({
|
navigator.credentials.get({
|
||||||
publicKey: makeAssertionOptions.publicKey
|
publicKey: makeAssertionOptions.publicKey
|
||||||
|
@ -56,14 +56,14 @@ function verifyAssertion(assertedCredential) {
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
id: assertedCredential.id,
|
id: assertedCredential.id,
|
||||||
rawId: bufferEncode(rawId),
|
rawId: encodeURLEncodedBase64(rawId),
|
||||||
type: assertedCredential.type,
|
type: assertedCredential.type,
|
||||||
clientExtensionResults: assertedCredential.getClientExtensionResults(),
|
clientExtensionResults: assertedCredential.getClientExtensionResults(),
|
||||||
response: {
|
response: {
|
||||||
authenticatorData: bufferEncode(authData),
|
authenticatorData: encodeURLEncodedBase64(authData),
|
||||||
clientDataJSON: bufferEncode(clientDataJSON),
|
clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
|
||||||
signature: bufferEncode(sig),
|
signature: encodeURLEncodedBase64(sig),
|
||||||
userHandle: bufferEncode(userHandle),
|
userHandle: encodeURLEncodedBase64(userHandle),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
contentType: 'application/json; charset=utf-8',
|
contentType: 'application/json; charset=utf-8',
|
||||||
|
@ -85,14 +85,21 @@ function verifyAssertion(assertedCredential) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode an ArrayBuffer into a base64 string.
|
// Encode an ArrayBuffer into a URLEncoded base64 string.
|
||||||
function bufferEncode(value) {
|
function encodeURLEncodedBase64(value) {
|
||||||
return encode(value)
|
return encode(value)
|
||||||
.replace(/\+/g, '-')
|
.replace(/\+/g, '-')
|
||||||
.replace(/\//g, '_')
|
.replace(/\//g, '_')
|
||||||
.replace(/=/g, '');
|
.replace(/=/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dccode a URLEncoded base64 to an ArrayBuffer string.
|
||||||
|
function decodeURLEncodedBase64(value) {
|
||||||
|
return decode(value
|
||||||
|
.replace(/_/g, '/')
|
||||||
|
.replace(/-/g, '+'));
|
||||||
|
}
|
||||||
|
|
||||||
function webauthnRegistered(newCredential) {
|
function webauthnRegistered(newCredential) {
|
||||||
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
|
const attestationObject = new Uint8Array(newCredential.response.attestationObject);
|
||||||
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
|
const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON);
|
||||||
|
@ -104,11 +111,11 @@ function webauthnRegistered(newCredential) {
|
||||||
headers: {'X-Csrf-Token': csrfToken},
|
headers: {'X-Csrf-Token': csrfToken},
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
id: newCredential.id,
|
id: newCredential.id,
|
||||||
rawId: bufferEncode(rawId),
|
rawId: encodeURLEncodedBase64(rawId),
|
||||||
type: newCredential.type,
|
type: newCredential.type,
|
||||||
response: {
|
response: {
|
||||||
attestationObject: bufferEncode(attestationObject),
|
attestationObject: encodeURLEncodedBase64(attestationObject),
|
||||||
clientDataJSON: bufferEncode(clientDataJSON),
|
clientDataJSON: encodeURLEncodedBase64(clientDataJSON),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
|
@ -184,11 +191,11 @@ function webAuthnRegisterRequest() {
|
||||||
}).done((makeCredentialOptions) => {
|
}).done((makeCredentialOptions) => {
|
||||||
$('#nickname').closest('div.field').removeClass('error');
|
$('#nickname').closest('div.field').removeClass('error');
|
||||||
|
|
||||||
makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge);
|
makeCredentialOptions.publicKey.challenge = decodeURLEncodedBase64(makeCredentialOptions.publicKey.challenge);
|
||||||
makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id);
|
makeCredentialOptions.publicKey.user.id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.user.id);
|
||||||
if (makeCredentialOptions.publicKey.excludeCredentials) {
|
if (makeCredentialOptions.publicKey.excludeCredentials) {
|
||||||
for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) {
|
for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) {
|
||||||
makeCredentialOptions.publicKey.excludeCredentials[i].id = decode(makeCredentialOptions.publicKey.excludeCredentials[i].id);
|
makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue