Claims-based Web Authentication
npm install webauthn-perkFastify plugin for supporting the Web
Authentication Perk pattern (thanks to
Emil Lundberg for
rephrasing
my original description):
1. Alice (an admin) chooses an unguessable ID.
2. Alice configures webauthn-perk on her Web server with the ID.
3. Alice uses her Web browser to visit a URL on her Web server which
contains the ID.
4. Alice uses the Web Authentication API to sign a challenge generated
on her server.
5. The signature and Alice’s public key are sent to her server.
6. Alice’s server verfies the signature.
7. Alice’s server associates her public key with the ID.
8. Alice uses client-side script to generate an unsigned JWT containing
claims of her choosing.
9. Alice uses the Web Authentication API to generate a signed
assertion, with the unsigned JWT as the challenge.
10. Alice sends the assertion to ordinary user Bob (by some means).
11. Bob uses his Web browser to visit a well-known URL on Alice’s Web
server.
12. Bob presents the assertion to Alice’s server.
13. Alice’s server verifies the assertion using Alice’s public key.
14. Bob receives a perk, i.e. Alice’s Web server provides some service
to Bob.
Note: From version 6.0.0, webauthn-perk uses
WebAuthn4JS instead of
fido2-lib and the
API has changed accordingly.
1. Run test/example.js on your server
(node test/example.js).
2. Visit the URL it displays in your WebAuthn-supporting browser.
- Either click through the certificate warnings or add
test/keys/ca.crt to your browser’s trusted
certificate authorities.
- The source to this page is in
test/fixtures/example.html and
test/fixtures/example.js. They make
use of a utility class in
dist/perk-workflow.js which calls the
Web Authentication API and communicates with the server. You can
re-use this in your projects — there’s documentation
below.
3. Use your security token to register or authenticate.
4. Type in a message and click Generate.
5. Use your security token to sign the message.
6. You’ll be shown another link. Open this in a different browser
(doesn’t have to support WebAuthn).
7. You should see your message.
8. Repeat with different messages as you like.
Register webauthn-perk with Fastify as normal. The available options
and their defaults are described below.
`` javascript
import webauthn_perk from 'webauthn-perk';
fastify.register(webauthn_perk, {
webauthn_perk_options: {
authorize_jwt_options: {
// The following are supported by AuthorizeJWT
// See https://github.com/davedoesdev/authorize-jwt#moduleexportsconfig-cb
db_dir: 'node_modules/pub-keystore/pouchdb/store/pub-keys', // You should override this
db_type: 'pouchdb',
db_for_update: true,
no_changes: true,
no_updates: true,
WEBAUTHN_MODE: true,
async on_authz(unused_authz) {
// Receives the AuthorizeJWT instance once it's constructed
},
// The following are required by WebAuthn4JS. They have no default and you must supply them.
// See http://rawgit.davedoesdev.com/davedoesdev/webauthn4js/master/docs/types/Config.html
RPDisplayName: undefined,
RPID: undefined,
RPOrigins: undefined
},
cred_options: {
valid_ids: [], // List of unguessable IDs (strings)
prefix: '/cred', // Unguessable ID paths are prefixed with this plus /
session_data_timeout: 60000, // Server challenges expire after this (ms). Note the session data is returned in the JSON responses, NOT in cookies.
store_prefix: false, // Whether to store complete path from root when associating unguessable IDs with public keys
default_user: {
// You can override these but they'll only be used if your authenticator supports storing user handles.
// See http://rawgit.davedoesdev.com/davedoesdev/webauthn4js/master/docs/types/User.html
id: 'anonymous',
name: 'Anonymous',
displayName: 'Anonymous'
},
users: {}, // If you want to customise user details per unguessable ID
registration_options: [
// See http://rawgit.davedoesdev.com/davedoesdev/webauthn4js/master/docs/interfaces/WebAuthn4JS.html#beginRegistration
],
login_options: [
// See http://rawgit.davedoesdev.com/davedoesdev/webauthn4js/master/docs/interfaces/WebAuthn4JS.html#beginLogin
]
},
perk_options: {
prefix: '/perk', // Well-known path for presenting assertions is this plus /
handler: function (info, request, reply) {
// This function is called after an assertion is successfully verified.
// You must override this or perk requests will fail.
// request and reply are standard Fastify objects and your function
// is treated as a standard route handler.
// info contains payload, uri, rev and credential properties
// as described for the cb parameter here:
// https://github.com/davedoesdev/authorize-jwt#authorizejwtprototypeauthorizeauthz_token-algorithms-cb
// The uri property is the unguessable ID associated with the public
// key that generated the assertion.
throw new Error('missing handler');
},
response_schema: undefined, // JSON schema for handler responses
payload_schema: undefined // JSON schema for the payload in the unsigned JWT contained in that are presented
}
}
});
`
The following routes will be added to your server. All request and
response bodies should be JSON-encoded.
- /cred/id/ for each id inwebauthn_perk_options.cred_options.valid_ids
- GET requests:
- If no public key is associated with id then the responseCredentialCreation
status is 404 and the body will contain a
SessionData
and an encrypted
beginRegistration
returned by
.CredentialCreation
The can be used when callingnavigator.credentials.create
in a browser. TheSessionData
must be used in a subsequent PUT request (see
below).
- If a public key has been associated with id then theCredentialAssertion
response status is 200 and the body will contain an issuer
ID (identifes the public key to the server), a
SessionData
and an encrypted
beginLogin
returned by
.CredentialAssertion
The can be used when callingnavigator.credentials.get
in a browser. The SessionData
must be used in a subsequent POST request (see below).
- PUT requests:
- The request body should contain a
CredentialCreationResponse
generated by navigator.credentials.create in a browser.navigator.credentials.create
You should have made a GET request previously to obtain the
options required by .
- If the creation response does not verify or is invalid then
the response status is 400.
- If a public key is already associated with id then the
response status is 409.
- Otherwise the public key contained in the creation response
is associated with id and the response status is 200.CredentialAssertion
The response body will contain the issuer ID (identifies the
public key to the server) and a
(identifies the public key to the browser).
- POST requests:
- The request body should contain a
CredentialAssertionResponse
generated by navigator.credentials.get in a browser. Younavigator.credentials.get
should have made a GET request previously to obtain the
options required by .
- If no public key is associated with id then the response
status is 404.
- If the assertion response does not verify using the public
key associated with id or is invalid then the response
status is 400.
- Otherwise the response status is 204 and the body is empty.
- Use this route to check you have access to the private key
which corresponds to the public key that the server has
associated with id.
- /perk/
- POST requests:
- The request body should contain an issuer ID (obtained from
a previous GET or PUT request to /cred/id/) and aCredentialAssertionResponse
navigator.credentials.get
generated by in a browser.
- The challenge used to generate the assertion response should
be an unsigned JWT. The request body is passed to
authorize-jwt
for verification.
- If the issuer ID does not identify a public key or the
assertion response does not verify using the public key
identified by the issuer ID then the response status is 400.
- Otherwise webauthn_perk_options.perk_options.handler is
called.
- GET requests:
- The request should have a single parameter, assertion,/perk/
containing the same JSON-encoded data required by POST
requests to (issuer ID and assertion response).
- The assertion is passed to the POST route handler for/perk/
.
- The response is the same as described above for POST
requests for /perk/.
JSON schemas for these routes can be found in
dist/schemas.js.
dist/perk-workflow.js contains a class,
PerkWorkflow, which you can use from your browser-side Javascript to
call the Web Authentication API and communicate with your server.
The script is an ES2015 module so you should include it using