WebAuthnCredentialRepository

WebAuthnCredentialRepository

Overview

A critical component of the plugin is the credential repository, which stores and loads credential registrations. The default credential repository utilizes the Shibboleth Storage Service, but it's also possible to utilize other repository implementations by extending the WebAuthnCredentialRepository interface. Any storage service indicated by the IdP property shibboleth.StorageService will be used. However, you can override this by specifying a different bean in the idp.authn.webauthn.StorageService property in conf/authn/webauthn.properties.

In theory, any implementation of a storage service should be compatible, but it's important to consider its capabilities before using it. For example, for testing, you can use client-storage by referencing the bean shibboleth.ClientSessionStorageService. But that will store your credential registrations in the browser and is not portable across browsers—although the credentials will survive an IdP restart so it might be useful during initial testing.

Credential Lookup Caches(1.3.0)

Some WebAuthn assertion validation and attestation registration functions require credential lookups by either userHandle or credentialId. These operations can be costly when performed over the storage service by brute force since credential registrations are stored as serialized JSON documents. To enhance lookup efficiency, version 1.3.0 introduces two secondary index caches to the default credential repository implementation that stores mappings between IdP users, credential userHandles, and credentialIds. These caches are enabled by default, but can be toggled using the property idp.authn.webauthn.StorageService.cache.enable in conf/authn/webauthn.properties. Both caches evict entries after a certain time has passed since they were last accessed. The expiry can be configured by adjusting the property idp.authn.webauthn.StorageService.cache.expireAfterAccess in the conf/authn/webauthn.properties file.

JDBC Storage Option

In production, you may want to consider using a JDBC storage option. Assuming you do not already have a database suitable for use with the Shibboleth Storage Service (if you do, you can skip to step 3), then:

  1. Install the JDBC storage plugin and create a new schema/database (e.g. webauthn) and a new table (e.g. webauthn.StorageRecords):

## This example is specific to MySQL ## Needed to support case sensitive queries in MySQL CREATE SCHEMA IF NOT EXISTS `webauthn` DEFAULT CHARACTER SET latin1 COLLATE latin1_general_cs ; CREATE TABLE webauthn.StorageRecords ( context varchar(255) NOT NULL, id varchar(255) NOT NULL, expires bigint DEFAULT NULL, value text NOT NULL, version bigint NOT NULL, PRIMARY KEY (context, id) );
  1. Configure a suitable JDBC connection by following the JDBC storage plugin documentation.

  2. Finally, set the storage service bean you want to use for WebAuthn using the property idp.authn.webauthn.StorageService in conf/authn/webauthn.properties

 

Accelerating JDBC Queries(1.3.0)

By default, queries to the storage service are not optimized for database lookups—this is a feature that makes all storage service implementations generic and interoperable. However, if you opt for a database storage solution, you may want or need to enable the JDBCAccelerator, especially as the number of credential registrations increases. The accelerator replaces some of the lookup features of the default credential repository with specialised, efficient lookups designed for databases that support SQL/JSON queries.

To enable the accelerator, you MUST ensure the following:

  • You are using a JDBC storage service for the credential repository (see the previous section).

  • You configure both the JDBCStorageService and JDBCAccelerator to use the same dataSource.

  • You are running a RDBMS that is compatible with SQL/JSON (SQL:2016) and, if you use the default queries, supports the JSON_TABLE function. At present, we are aware of support from:

    • MySQL >= v8 (2018)

    • Mariadb >= v10.6 (2021)

    • PostgreSQL >= 17 (2024)SQL:201SQL:2016

 

To enable, you need to define a shibboleth.authn.WebAuthn.JDBCAccelerator bean in the global context (conf/global.xml).

<bean id="WebAuthnJDBCAccelerator" parent="shibboleth.authn.WebAuthn.JDBCAccelerator" p:dataSource-ref="JDBCDataSource"/>

This will get picked up (by name) and injected into the credential repository by the credential repository factory. You can change the name of the bean by setting the property idp.authn.webauthn.StorageService.jdbcAccelerator in conf/authn/webauthn.properties.

The accelerator is designed to search within the JSON-serialised storage records of credential registrations to facilitate the lookup of credentials by credentialId and userHandle. The default queries use the JSON_TABLE function as shown in the table below:

Lookup

SQL

Lookup

SQL

userHandle

SELECT version, expires, value FROM StorageRecords, JSON_TABLE(value,'$[*]' COLUMNS (uid text PATH '$.userIdentity.id')) AS cred WHERE context=? AND cred.uid = ?

credentialId

SELECT version, expires, value FROM StorageRecords, JSON_TABLE(value,'$[*]' COLUMNS (cid text PATH '$.credential.credentialId')) AS cred WHERE context=? AND cred.cid = ?;

If you have a more efficient query or need adjustments for your RDBMS, you can define your own SQL in the accelerator:

<bean id="WebAuthnJDBCAccelerator" parent="shibboleth.authn.WebAuthn.JDBCAccelerator" p:dataSource-ref="JDBCDataSource" p:queryRecordsByUserHandleSQL="..." p:queryRecordsByCredentialIdSQL="..."/>

The SQL must SELECT the columns: version, expires, value, in that order. The results of the query should be credentials that match to the given userHandle, or the given credentialId. The correctness of the results returned are checked by the credential repository.

 

Credential Registration Data Model

The credential registration data model is captured by the CredentialRecord. This record is serialized into JSON for storage. An example record is shown below.

[ { "userIdentity": { "name": "name", "displayName": "disp name", "id": "IUTFXv137bZfIWr10YFSAGGGlt9U-A8Sj78SuyEjZFFyD8R10MxvzM1xOtu9D62kz32WFwwv" }, "username": "username", "transports": [ "internal" ], "registrationTime": 1731413429.716813000, "discoverable": true, "credential": { "credentialId": "fwrgERGewrgw_gwrhgwbhwbh", "userHandle": "FGWRGWgwrgwr-fwfwwfgFGWEGHWR", "publicKeyCose": "REGWgvwergtWERTwetGWWregw", "signatureCount": 0 }, "aaguid": "rc4AAjW8xgpkiwsl8fBVAw", "userVerified": false, "nickname": "My Credential" } ]

 

Reference

Name

Type

Default

Description

Name

Type

Default

Description

idp.authn.webauthn.StorageService

Bean-ref

shibboleth.StorageService

The storage service to use

idp.authn.webauthn.StorageService.jdbcAccelerator1.3.0

Bean-ref

WebAuthnJDBCAccelerator

The name of the JDBC accelerator bean to use.

idp.authn.webauthn.StorageService.jdbcAccelerator.defaultType1.3.0

QUERY | READALL

QUERY

The type of default accelerator.

idp.authn.webauthn.StorageService.cache.enable1.3.0

boolean

true

enable or disable the userHandle and credentialId lookup caches

idp.authn.webauthn.StorageService.cache.expireAfterAccess1.3.0

Duration

PT60M

How long to wait from the last access before expiring cache entries.