Paswordless UI redone

This commit is contained in:
konarfil
2023-10-10 13:39:21 +02:00
parent daf109a6bf
commit d7d2b94dea
2 changed files with 341 additions and 103 deletions

View File

@@ -12,138 +12,374 @@
padding: 10px;
margin: 10px 0;
font-family: monospace;
overflow-y: scroll;
}
.step {
display: none;
}
.button-label {
padding: 20px 0 5px 0;
}
.card-header {
padding-bottom: 20px;
}
.collection {
margin: .5rem 0 1rem 0;
border: 1px solid #e0e0e0;
border-radius: 2px;
overflow: hidden;
position: relative;
}
</style>
{/add-header}
<div class="container">
<div class="section" id="init">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6>Create a new credential or use an existing one</h6>
<p>First the user enters their username:</p>
<input id="userName" placeholder="User name"/><br/>
<p>If they are a new user, they need to generate a new credential and register it with the application</p>
<button class="nextBtn waves-effect waves-light btn full-width" id="register">Register</button>
<p>If they are a returning user, their authenticator must prove they own the credential.</p>
<button class="nextBtn waves-effect waves-light btn full-width" id="login">Login</button>
<div class="row" id="init">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6 class="card-header"><b>1.</b> Create a new registration or use an existing one</h6>
<p>First the user enters their username:</p>
<input id="userName" placeholder="User name"/><br/>
<p class="button-label">New user needs to generate a new credential and register it with the application.</p>
<button class="nextBtn waves-effect waves-light btn full-width" id="register">Register</button>
<p class="button-label">Returning user must prove they own the credential.</p>
<button class="nextBtn waves-effect waves-light btn full-width" id="login">Login</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section step" id="server1-request">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6>Request a challenge</h6>
<p>The interaction starts with an AJAX call.</p>
<div class="code">POST <span id="server1-url"></span><br><div id="server1-call"></div></div>
<button class="nextBtn waves-effect waves-light btn full-width">Request challenge</button>
<div class="row step" id="server1-request">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6 class="card-header"><b>2.</b> Request a challenge</h6>
<p>The interaction starts with an AJAX call:</p>
<div class="code">POST <span id="server1-url"></span><br><div id="server1-call"></div></div>
<p class="button-label">Basically we are just specifying the username and his display name</p>
<button class="nextBtn waves-effect waves-light btn full-width">Request challenge</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section step" id="server1-response">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6>The challenge</h6>
<p>The server prepares a challenge for the browser to sign.</p>
<div class="code" id="server1-response-body"></div>
<button class="nextBtn waves-effect waves-light btn full-width">Continue</button>
<div class="row step" id="server1-response">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6 class="card-header"><b>3.</b> The challenge</h6>
<p>The server prepares a challenge for the browser to sign:</p>
<div class="code" id="server1-response-body"></div>
<p class="button-label">Let's break it down...</p>
<ul id="register-challenge-breakdown" class="collection" style="display: none;">
<li class="collection-item">
<p class="emphasis"><b>rp</b></p>
<p>
Information about the relying party. The ID of the relying party is usually the domain name of the website or service trying to authenticate the user. Name contains human-friendly name for the relying party.
</p>
</li>
<li class="collection-item">
<p class="emphasis">user</p>
<p>
Information about the user: <b>id</b> contains unique identifier in the form of base64 encoded byte array. Parameters <b>name</b> and <b>displayName</b> are taken from the initial request.
</p>
</li>
<li class="collection-item">
<p class="emphasis">challenge</p>
<p>
A random value generated by the relying party. It's used to ensure that the authentication request is fresh and not a replay of a previous one.
</p>
</li>
<li class="collection-item">
<p class="emphasis">pubKeyCredParams</p>
<p>
A list of public key credential types and cryptographic algorithm combinations supported by the relying party.
</p>
</li>
<li class="collection-item">
<p class="emphasis">authenticatorSelection</p>
<p>Criteria for the authenticator:</p>
<p>
<b>requireResidentKey:</b> This indicates if the credential should be stored on the authenticator (like a hardware token or platform). Here, it's set to "false", meaning resident keys aren't mandatory.
</p>
<p>
<b>userVerification:</b> This describes the desired user verification method. "discouraged" means the relying party does not want user verification employed during the creation process.
</p>
</li>
<li class="collection-item">
<p class="emphasis">timeout</p>
<p>
Maximum time, in milliseconds, that the caller is willing to wait for the call to complete.
</p>
</li>
<li class="collection-item">
<p class="emphasis">attestation</p>
<p>
Specifies the attestation conveyance preference. "none" means the relying party is not interested in authenticator attestation.
</p>
</li>
<li class="collection-item">
<p class="emphasis">extensions</p>
<p>
Dictionary of extension identifiers with their associated data. In this case, there's an extension "txAuthSimple" with no associated data provided.
</p>
</li>
</ul>
<ul id="login-challenge-breakdown" class="collection" style="display: none;">
<li class="collection-item">
<p class="emphasis">challenge</p>
<p>
A random value generated by the relying party. It's used to ensure that the authentication request is fresh and not a replay of a previous one.
</p>
</li>
<li class="collection-item">
<p class="emphasis">timeout</p>
<p>
Specifies the time, in milliseconds, that the caller is willing to wait for the call to complete.
</p>
</li>
<li class="collection-item">
<p class="emphasis">rpId</p>
<p>
The Relying Party Identifier. It helps the client (browser) know which key to use if there's more than one for a given domain.
</p>
</li>
<li class="collection-item">
<p class="emphasis">userVerification</p>
<p>
This specifies the desired user verification method. The value "discouraged" suggests that the website doesn't require a strong verification of the user, which means if the authenticator has a user verification method (like a fingerprint scanner or PIN), it's not mandatory to use it for this authentication.
</p>
</li>
<li class="collection-item">
<p class="emphasis">extensions</p>
<p>
Dictionary of extension identifiers with their associated data. In this case, there's an extension "txAuthSimple" with no associated data provided.
</p>
</li>
<li class="collection-item">
<p class="emphasis">allowCredentials</p>
<p>
An array containing one or more credentials that are allowed for the authentication:
</p>
<ul class="collection">
<li class="collection-item">
<p class="emphasis">type</p>
<p>
Specifies the type of the public key credential.
</p>
</li>
<li class="collection-item">
<p class="emphasis">id</p>
<p>
The credential ID that identifies the public key to be used during the authentication.
</p>
</li>
<li class="collection-item">
<p class="emphasis">transports</p>
<p>
Lists the allowed transports to retrieve the credential ID. This is useful for guiding the user's client (browser) about how it can communicate with the authenticator.
</p>
</li>
</ul>
</li>
</ul>
<button class="nextBtn waves-effect waves-light btn full-width">Continue</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section step" id="navigator-request">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6>WebAuthn API</h6>
<p>The challenge is passed to the Javascript call:</p>
<div class="code" id="navigator-call"></div>
<button class="nextBtn waves-effect waves-light btn full-width">Call Webauthn API</button>
<div class="row step" id="navigator-request">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6 class="card-header"><b>4.</b> WebAuthn API</h6>
<p>The challenge is passed to the Javascript call:</p>
<div class="code" id="navigator-call"></div>
<button class="nextBtn waves-effect waves-light btn full-width">Call Webauthn API</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section step" id="navigator-attestation">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6>Attestation</h6>
Which responds:
<div class="code" id="navigator-attestation-body"></div>
The <strong>response.clientDataJSON</strong> is base64 encoded:
<div class="code" id="navigator-attestation-clientDataJSON"></div>
The <strong>response.attestationObject</strong> is <a href="https://cbor.io/">CBOR</a> encoded and contains a public key and metadata:
<div class="code" id="navigator-attestationObject" style="height: 150px;"></div>
<button class="nextBtn waves-effect waves-light btn full-width">Finish the interaction</button>
<div class="row step" id="navigator-attestation">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6 class="card-header"><b>5.</b> Attestation</h6>
<p style="padding-bottom: 5px;">Which responds:</p>
<div class="code" id="navigator-attestation-body"></div>
<p class="button-label">Let's break it down...</p>
<ul class="collection">
<li class="collection-item">
<p class="emphasis"><b>id</b></p>
<p>
A base64url encoded string representation of the rawId. It uniquely identifies the created credential.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>rawId</b></p>
<p>
The raw identifier of the credential, typically a byte array. It's the same as the id but in its raw binary form.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>attestationObject</b></p>
<p>
This is a base64 encoded binary representation of an attestation statement. The attestation statement is produced by the authenticator to prove to the relying party (e.g., a website) that a new public key credential has been created in the authenticator. It typically contains details about the authenticator, a freshly generated public key for the user, some metadata, and a signature from the authenticator. It is <a href="https://cbor.io/">CBOR</a> encoded.
</p>
<div class="code" id="navigator-attestationObject" style="height: 150px; overflow-y: scroll;"></div>
</li>
<li class="collection-item">
<p class="emphasis"><b>clientDataJSON</b></p>
<p>
This is a base64 encoded JSON string that contains information about the context in which the attestation was generated. Let's break down its decoded content:
</p>
<div class="code" id="navigator-attestation-clientDataJSON"></div>
<p class="button-label">Let's break it down...</p>
<ul class="collection">
<li class="collection-item">
<p class="emphasis"><b>challenge</b></p>
<p>
This is the challenge that was sent by the relying party during the registration request. The authenticator's response must include this challenge to ensure the action was based on a recent request and not a replayed one.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>origin</b></p>
<p>
The origin of the request, indicating where the request came from.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>type</b></p>
<p>
This indicates the type of operation, which in this context is "webauthn.create", meaning it's a credential creation operation.
</p>
</li>
</ul>
</li>
<li class="collection-item">
<p class="emphasis"><b>type</b></p>
<p>
This specifies the type of the public key credential, and it's set to "public-key" which means it's a public key credential.
</p>
</li>
</ul>
<p class="button-label">We finish the interaction by...</p>
<button class="nextBtn waves-effect waves-light btn full-width">Finish the interaction</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section step" id="navigator-assertion">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6>Assertion</h6>
Which responds:
<div class="code" id="navigator-assertion-body"></div>
The <strong>response.clientDataJSON</strong> is base64 encoded:
<div class="code" id="navigator-assertion-clientDataJSON"></div>
The <strong>response.signature</strong> is derived from <strong>clientDataJSON</strong> and <strong>authenticatorData</strong>, using the private key stored on authenticator and validated public key stored on the server
<button class="nextBtn waves-effect waves-light btn full-width">Finish the interaction</button>
<div class="row step" id="navigator-assertion">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<h6 class="card-header"><b>5.</b> Attestation</h6>
<p style="padding-bottom: 5px;">Which responds:</p>
<div class="code" id="navigator-assertion-body"></div>
<p class="button-label">Let's break it down...</p>
<ul class="collection">
<li class="collection-item">
<p class="emphasis"><b>id</b></p>
<p>
A base64url encoded string representation of the rawId. It uniquely identifies the created credential.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>rawId</b></p>
<p>
The raw identifier of the credential, typically a byte array. It's the same as the id but in its raw binary form.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>clientDataJSON</b></p>
<p>
This is a base64 encoded JSON string that contains information about the context in which the attestation was generated. Let's break down its decoded content:
</p>
<div class="code" id="navigator-assertion-clientDataJSON"></div>
<p class="button-label">Let's break it down...</p>
<ul class="collection">
<li class="collection-item">
<p class="emphasis"><b>challenge</b></p>
<p>
This is the challenge that was sent by the relying party during the registration request. The authenticator's response must include this challenge to ensure the action was based on a recent request and not a replayed one.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>origin</b></p>
<p>
The origin of the request, indicating where the request came from.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>type</b></p>
<p>
This indicates the type of operation, which in this context is "webauthn.create", meaning it's a credential creation operation.
</p>
</li>
</ul>
</li>
<li class="collection-item">
<p class="emphasis"><b>authenticatorData</b></p>
<p>
This contains information about the authentication event. It typically includes the hash of the <b>clientDataJSON</b>, a sign count (to protect against clone attacks), and other data relevant to the authentication process.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>signature</b></p>
<p>
This is the authenticator's digital signature over the combination of the <b>authenticatorData</b> and the hash of the <b>clientDataJSON</b>. The website will use this to verify that the response came from the user's authenticator and corresponds to the challenge it issued.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>userHandle</b></p>
<p>
An optional field which, if present, represents a unique identifier for the user, established during registration. It helps websites identify which user is trying to authenticate, especially in scenarios where the user doesn't explicitly provide a username during the login process.
</p>
</li>
<li class="collection-item">
<p class="emphasis"><b>type</b></p>
<p>
This specifies the type of the public key credential, and it's set to "public-key" which means it's a public key credential.
</p>
</li>
</ul>
<p class="button-label">We finish the interaction by...</p>
<button class="nextBtn waves-effect waves-light btn full-width">Finish the interaction</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="section step" id="errors">
<div class="col s12 m7">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<div id="result"></div>
<div id="trace"></div>
<button class="resetBtn waves-effect waves-light btn full-width">Start over</button>
<div class="row step" id="errors">
<div class="col s12 m8 offset-m2">
<div class="card horizontal">
<div class="card-stacked">
<div class="card-content">
<div id="result"></div>
<div id="trace"></div>
<button class="resetBtn waves-effect waves-light btn full-width">Start over</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<form action="#" method="POST">
<input name="sessionId" type="hidden" value="{sessionId}">
<div id="form-generated"></div>
</form>
<form action="#" method="POST">
<input name="sessionId" type="hidden" value="{sessionId}">
<div id="form-generated"></div>
</form>
</div>
<script type="text/javascript">
@@ -216,6 +452,8 @@
function traceRegisterResponse(params) {
$("#server1-response").showInViewport();
$("#server1-response-body").html(JSON.stringify(params, null, 2));
$("#register-challenge-breakdown").show();
return continueButton("#server1-response", params);
}
@@ -230,6 +468,7 @@
function traceLoginResponse(params) {
$("#server1-response").showInViewport();
$("#server1-response-body").html(JSON.stringify(params, null, 2));
$("#login-challenge-breakdown").show();
return continueButton("#server1-response", params);
}
@@ -287,8 +526,6 @@
callbackPath: '/q/webauthn/callback',
registerPath: '/q/webauthn/register',
loginPath: '/q/webauthn/login',
// loginCallbackPath: '/webauthn/login',
// registerCallbackPath: '/webauthn/register',
debuggingFunction: tracer
});
@@ -323,12 +560,9 @@
registerButton.onclick = () => {
var userName = document.getElementById('userName').value;
// var firstName = document.getElementById('firstName').value;
// var lastName = document.getElementById('lastName').value;
result.replaceChildren();
webAuthn.registerOnly({ name: userName, displayName: userName /*firstName + " " + lastName*/})
webAuthn.registerOnly({ name: userName, displayName: userName})
.then(body => {
form("/auth/passwordless/register", {
'webAuthnId': body.id,
@@ -347,4 +581,3 @@
};
</script>
{/include}

View File

@@ -36,7 +36,7 @@
background-color: #007BFF;
}
.btn:hover {
.btn:hover, .btn:focus {
background-color: #0056b3
}
@@ -56,6 +56,11 @@
.collection-item {
border: none;
}
.emphasis {
color: #0056b3;
font-weight: bold;
}
</style>
{#insert add-header}{/}
</head>