Documentation
Overview
Shark WebAuthn .NET library provides a server-side implementation of the WebAuthn standard, enabling secure passwordless and multi-factor authentication (MFA) for web applications. It supports key WebAuthn operations – credential registration and authentication – ensuring compliance with the WebAuthn Level 2 specification (Web Authentication: An API for accessing Public Key Credentials Level 2). Shark WebAuthn is written in .NET 8, making it suitable for modern ASP.NET Core applications and environments.
Package Installation
To begin integrating Shark WebAuthn into your ASP.NET Core application, add the following NuGet packages to your project:
dotnet add package Shark.Fido2.Core
dotnet add package Shark.Fido2.Models
dotnet add package Shark.Fido2.InMemory
These packages provide the core WebAuthn functionality, data models, and an in-memory credential repository for development and testing. For production, consider using a persistent credential store instead of the in-memory implementation. See the Persistent Datastores section for details on integrating with Microsoft SQL Server and Amazon DynamoDB.
Server-side Configuration
Shark WebAuthn .NET library requires specific configuration to operate as a WebAuthn relying party. Configuration is typically provided via the Fido2Configuration
section in your application's configuration files (e.g., appsettings.json
, appsettings.Production.json
). This section details all available configuration options, their default values, and their intended usage.
Configuration Schema
The following is an example of the server-side configuration.
{
"Fido2Configuration": {
"RelyingPartyId": "example.com", // Use 'localhost' for local development
"RelyingPartyIdName": "Example Corporation",
"Origins": [ "https://example.com" ], // Use '[ "localhost" ]' for local development
"Timeout": "60000",
"AllowNoneAttestation": true,
"AllowSelfAttestation": true,
"EnableTrustedExecutionEnvironmentOnly": false,
"EnableMetadataService": true,
"EnableStrictAuthenticatorVerification": false,
"MetadataServiceConfiguration": {
"MetadataBlobLocation": "https://mds3.fidoalliance.org/",
"RootCertificateLocationUrl": "http://secure.globalsign.com/cacert/root-r3.crt",
"MaximumTokenSizeInBytes": 6291456
}
}
}
A minimal server-side configuration example is shown below.
{
"Fido2Configuration": {
"RelyingPartyId": "example.com", // Use 'localhost' for local development
"RelyingPartyIdName": "Example Corporation",
"Origins": [ "https://example.com" ] // Use '[ "localhost" ]' for local development
}
}
Property Reference
Core Configuration
Option | Default | Description |
---|---|---|
RelyingPartyId | Valid domain string identifying the Relying Party on whose behalf a given registration or authentication ceremony is being performed. This is a critical parameter in the WebAuthn protocol. It defines the security scope within which credentials are valid. Therefore, careful selection is essential, as an incorrect or overly broad value can lead to unintended credential reuse or security vulnerabilities. | |
RelyingPartyIdName | Human-palatable identifier for the Relying Party, intended only for display. | |
Origins | List of the fully qualified origins of the Relying Party making the request, passed to the authenticator by the browser. | |
Timeout | 60000 | Time, in milliseconds, that the Relying Party is willing to wait for the call to complete. |
AllowNoneAttestation | true | Value indicating whether None attestation type is acceptable under Relying Party policy. None attestation is used when the authenticator doesn't have any attestation information available. |
AllowSelfAttestation | true | Value indicating whether Self attestation type is acceptable under Relying Party policy. Self attestation is used when the authenticator doesn't have a dedicated attestation key pair or a vendor-issued certificate. |
EnableTrustedExecutionEnvironmentOnly | true | Value indicating whether the Relying Party trusts only keys that are securely generated and stored in a Trusted Execution Environment (Android Key Attestation). |
EnableMetadataService | true | Value indicating whether the Relying Party uses the Metadata Service to verify the attestation object. |
EnableStrictAuthenticatorVerification | false | Value indicating whether the Relying Party requires strict verification of authenticators. If enabled, missing metadata for the authenticator would cause attestation to fail. |
FIDO Metadata Service Configuration
Option | Default | Description |
---|---|---|
MetadataBlobLocation | https://mds3.fidoalliance.org/ | Location of the centralized and trusted source of information about FIDO authenticators (Metadata Service BLOB). |
RootCertificateLocationUrl | http://secure.globalsign.com/cacert/root-r3.crt | Location of GlobalSign Root R3 for Metadata Service BLOB. |
MaximumTokenSizeInBytes | 6291456 | Maximum token size in bytes that will be processed. This configuration is related to the Metadata Service BLOB size. |
Configuration Usage
Add the Fido2Configuration
section to your appsettings.json
or environment-specific configuration file.
Best Practices
- EnableMetadataService is recommended for production environments to ensure authenticators are validated against the FIDO Metadata Service.
Troubleshooting
- If authentication fails with an "origin" error, verify that the
Origins
array matches the actual origin of your frontend application. - If using the Metadata Service, ensure your application can reach the URLs specified in
MetadataServiceConfiguration
.
Dependency Registration
To register the Shark WebAuthn services in your ASP.NET Core application, add the following lines to your Program.cs
file:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Other service registrations
builder.Services.AddFido2InMemoryStore();
builder.Services.AddFido2(builder.Configuration);
var app = builder.Build();
// Configure the HTTP request pipelines
// HTTP request pipelines
app.UseStaticFiles();
// Other HTTP request pipelines
app.MapControllers();
await app.RunAsync();
AddFido2(builder.Configuration)
registers the core Shark WebAuthn services using your application's configuration. AddFido2InMemoryStore()
registers an in-memory credential repository, suitable for development and testing. For production, consider using a persistent credential store instead of the in-memory implementation.
Server-side API
To enable WebAuthn functionality, your application requires REST API controllers to handle core WebAuthn operations: credential registration (attestation) and authentication (assertion). These controllers expose endpoints for frontend interaction with the WebAuthn flows.
Attestation (Registration)
The Attestation controller handles the registration ceremony.
1. Create the AttestationController
and inject an instance of the IAttestation
interface into its constructor
[Route("[controller]")]
[ApiController]
public class AttestationController : ControllerBase
{
private readonly IAttestation _attestation;
public AttestationController(IAttestation attestation)
{
_attestation = attestation;
}
// Endpoints for attestation operations
}
2. Add endpoint to get create credential options
[HttpPost("options")]
public async Task<IActionResult> Options(ServerPublicKeyCredentialCreationOptionsRequest request, CancellationToken cancellationToken)
{
var createOptions = await _attestation.CreateOptions(request.Map(), cancellationToken);
var response = createOptions.Map();
HttpContext.Session.SetString("CreateOptions", JsonSerializer.Serialize(createOptions));
return Ok(response);
}
3. Add endpoint to create credential
[HttpPost("result")]
public async Task<IActionResult> Result(ServerPublicKeyCredentialAttestation request, CancellationToken cancellationToken)
{
var createOptionsString = HttpContext.Session.GetString("CreateOptions");
var createOptions = JsonSerializer.Deserialize<PublicKeyCredentialCreationOptions>(createOptionsString!);
await _attestation.Complete(request.Map(), createOptions!, cancellationToken);
return Ok(ServerResponse.Create());
}
Assertion (Authentication)
The Assertion controller handles the authentication ceremony.
1. Create the AssertionController
and inject an instance of the IAssertion
interface into its constructor
[Route("[controller]")]
[ApiController]
public class AssertionController : ControllerBase
{
private readonly IAssertion _assertion;
public AssertionController(IAssertion assertion)
{
_assertion = assertion;
}
// Endpoints for assertion operations
}
2. Add endpoint to get request credential options
[HttpPost("options")]
public async Task<IActionResult> Options(ServerPublicKeyCredentialGetOptionsRequest request, CancellationToken cancellationToken)
{
var requestOptions = await _assertion.RequestOptions(request.Map(), cancellationToken);
var response = requestOptions.Map();
HttpContext.Session.SetString("RequestOptions", JsonSerializer.Serialize(requestOptions));
return Ok(response);
}
3. Add endpoint to validate credential
[HttpPost("result")]
public async Task<IActionResult> Result(ServerPublicKeyCredentialAssertion request, CancellationToken cancellationToken)
{
var requestOptionsString = HttpContext.Session.GetString("RequestOptions");
var requestOptions = JsonSerializer.Deserialize<PublicKeyCredentialRequestOptions>(requestOptionsString!);
await _assertion.Complete(request.Map(), requestOptions!, cancellationToken);
return Ok(ServerResponse.Create());
}
Important: These controller examples use session state to store options between requests. To enable session support in your application, ensure that Program.cs
includes calls to AddSession
and UseSession
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSession();
// Other service registrations
var app = builder.Build();
app.UseSession();
// Other HTTP request pipelines
While the default in-memory session state is sufficient for development and testing, it is recommended to use a distributed cache (such as Redis) in production environments.
Client-side Integration
To finalize the implementation, you must incorporate JavaScript code that interacts with the browser's Web Authentication API. This API manages the client-side authentication process. Please note that JavaScript files must be placed in the wwwroot/js
folder so the server can serve them as static assets.
1. Add the attestation.js
file containing the navigator.credentials.create()
invocation
The following is a minimal sample of JavaScript code for creating discoverable credentials using the Web Authentication API in the browser. This code demonstrates the basic flow for communicating with the server-side REST API endpoints described above. Important: This example does not include production-level safeguards for simplicity. For real-world applications, you should add proper error handling and input validation.
Click to expand
async function requestCreateCredentialOptions(username, displayName) {
const optionsRequest = {
username: username,
displayName: displayName,
attestation: 'direct',
authenticatorSelection: {
residentKey: 'required',
userVerification: 'required',
requireResidentKey: true
}
};
const options = await fetchAttestationOptions(optionsRequest);
await createCredential(options);
}
async function createCredential(options) {
const credentialCreationOptions = {
publicKey: {
rp: {
id: options.rp.id,
name: options.rp.name,
},
user: {
id: toUint8Array(options.user.id),
name: options.user.name,
displayName: options.user.displayName,
},
pubKeyCredParams: options.pubKeyCredParams.map(param => ({
type: param.type,
alg: param.alg,
})),
authenticatorSelection: options.authenticatorSelection,
challenge: toUint8Array(options.challenge),
excludeCredentials: options.excludeCredentials.map(credential => ({
id: toUint8Array(credential.id),
transports: credential.transports,
type: credential.type,
})),
timeout: options.timeout,
attestation: options.attestation
},
};
let attestation = await navigator.credentials.create(credentialCreationOptions);
const credentials = {
id: attestation.id,
rawId: toBase64Url(attestation.rawId),
response: {
attestationObject: toBase64Url(attestation.response.attestationObject),
clientDataJson: toBase64Url(attestation.response.clientDataJSON),
transports: attestation.response.getTransports(),
},
type: attestation.type,
};
await fetchAttestationResult(credentials);
window.alert('User was registered');
}
async function fetchAttestationOptions(optionsRequest) {
const response = await fetch('/attestation/options/', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(optionsRequest)
});
if (response.ok) {
return await response.json();
}
}
async function fetchAttestationResult(credentials) {
const response = await fetch('/attestation/result/', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(credentials)
});
}
window.requestCreateCredentialOptions = requestCreateCredentialOptions;
2. Add the assertion.js
file containing the navigator.credentials.get()
invocation
The following is a minimal sample of JavaScript code for performing authentication with discoverable credentials using the Web Authentication API in the browser. Important: This example does not include production-level safeguards for simplicity. For real-world applications, you should add proper error handling and input validation.
Click to expand
async function requestVerifyCredentialOptions() {
const optionsRequest = {};
const options = await fetchAssertionOptions(optionsRequest);
await requestCredential(options);
}
async function requestCredential(options) {
const credentialRequestOptions = {
publicKey: {
rpId: options.rpId,
challenge: toUint8Array(options.challenge),
allowCredentials: [],
timeout: options.timeout
},
};
let assertion = await navigator.credentials.get(credentialRequestOptions);
const credentials = {
id: assertion.id,
rawId: toBase64Url(assertion.rawId),
response: {
authenticatorData: toBase64Url(assertion.response.authenticatorData),
clientDataJson: toBase64Url(assertion.response.clientDataJSON),
signature: toBase64Url(assertion.response.signature),
userHandle: toBase64Url(assertion.response.userHandle),
},
type: assertion.type,
};
await fetchAssertionResult(credentials);
window.alert('User was authenticated');
}
async function fetchAssertionOptions(optionsRequest) {
const response = await fetch('/assertion/options/', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(optionsRequest)
});
if (response.ok) {
return await response.json();
}
}
async function fetchAssertionResult(credentials) {
const response = await fetch('/assertion/result/', {
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify(credentials)
});
}
window.requestVerifyCredentialOptions = requestVerifyCredentialOptions;
This JavaScript code bridges the browser's Web Authentication API with the server-side REST API endpoints provided by the ASP.NET Core controllers described above. More information about the Web Authentication API is available on the MDN Web Docs site at developer.mozilla.org page.
3. Add utilities functions
Add utility functions to convert between Base64URL strings and Uint8Array byte arrays, ensuring proper encoding and decoding for binary data used in web authentication operations.
4. Add HTML markup
Add the HTML markup to support credential registration and authentication. Refer to the sample Index.cshtml file for an example.
5. Add JavaScript logic for the site
Add JavaScript logic to connect the HTML markup with credential registration and authentication functionality. Refer to the sample site.js file for an example.
6. Reference JavaScript files
Reference JavaScript files in _Layout.cshtml
.
<script src="~/js/attestation.js" asp-append-version="true"></script>
<script src="~/js/assertion.js" asp-append-version="true"></script>
<script src="~/js/utilities.js" asp-append-version="true"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
7. Run the ASP.NET Core application over HTTPS
Browsers Support
Up-to-date details regarding the Web Authentication API support across modern browsers can be found on Can I use page.
Persistent Data Stores
For production environments, you should use a persistent credential store instead of the in-memory implementation. Shark WebAuthn provides support for various database providers.
Microsoft SQL Server
To use Microsoft SQL Server as your credential store, follow these steps:
1. Database Setup
Create the necessary database table by executing the SQL table creation script available at SQL Server Table Creation Script.
2. Package Installation
Add the following NuGet packages to your project:
dotnet add package Shark.Fido2.SqlServer
3. Dependency Registration
Replace the in-memory store registration with Microsoft SQL Server in your Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Other service registrations
builder.Services.AddFido2SqlServer();
builder.Services.AddFido2(builder.Configuration);
4. Configuration
Add the connection string to your appsettings.json
or environment-specific configuration file:
{
"ConnectionStrings": {
"DefaultConnection": "Server=SQL_SERVER_INSTANCE;Database=DATABASE_NAME;OTHER_PARAMETERS;"
}
}
Amazon DynamoDB
To use Amazon DynamoDB as your credential store, follow these steps:
1. Table Setup
Create the Amazon DynamoDB table with the following parameters:
- Table name:
Credential
- Partition key:
cid
(Binary
) - Sort key: N/A
-
Global secondary index name:
UserNameIndex
- Partition key:
un
(String
) - Projected properties: INCLUDE:
cid
,tsp
- Partition key:
Ensure your AWS credentials have the following permissions to access the Credential
table.
Actions
dynamodb:GetItem
dynamodb:Query
dynamodb:PutItem
dynamodb:UpdateItem
Resources
arn:aws:dynamodb:AWS_REGION:AWS_ACCOUNT_ID:table/Credential
arn:aws:dynamodb:AWS_REGION:AWS_ACCOUNT_ID:table/Credential/index/UserNameIndex
2. Package Installation
Add the following NuGet packages to your project:
dotnet add package Shark.Fido2.DynamoDB
3. Dependency Registration
Replace the in-memory store registration with Amazon DynamoDB in your Program.cs
:
var builder = WebApplication.CreateBuilder(args);
// Other service registrations
builder.Services.AddFido2DynamoDB();
builder.Services.AddFido2(builder.Configuration);
4. Configuration
Add the following Amazon DynamoDB configuration to your appsettings.json
or environment-specific configuration file:
{
"AmazonDynamoDbConfiguration": {
"AwsRegion": "",
"ConnectTimeoutInSeconds": 10,
"MaxErrorRetry": 3,
"AccessKey": "",
"SecretKey": ""
}
}