Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Blisache is a web service providing Fido2/webauthn authentication. It acts as a relying party for webauthn registration and authentication ceremonies. Passkeys and non-resident keys are both supported.

We provide client-side packages to help the integration of the API.

API endpoints are available at https://blisache.com/api/<api_endpoint>.

The API is built over HTTP and is composed of 4 main blocks which group endpoints by features :

  • Registration
  • Authentication
  • Credential manipulation
  • Signature verification

Each of these and their specific endpoints are documented in their own reference chapter.

ES256 and EdDSA signature algorithms are currently supported.

Registering your domain

The service is enabled for a domain by creating an account and registering your domain name on the admin website.

The website itself integrates the features exposed by the API. You create an account by registering a key using a Fido2 compatible device. In practice nearly all smartphones and many laptop have already this capability through fingerprint or facial recognition. Otherwise a security key can be used as well.

After creating an account and authenticating successfully, you get redirected to the domains page. This is where you can see your already registered domains and register new ones.

When registering a domain, an activation code is generated and needs to be copied in the domain’s DNS TXT records.

blisache-verification=myactivationkeyhere

A background task will regularly check for this activation code in the DNS TXT records of the domain and disable the service in case it is not found.

At that point the service is activated for your domain and you can start integrating the API.

Setup a local environment

Working with Blisache locally requires you have registered and activated your domain. In this example I will create the subdomain dev.blisache.com and I will activate this subdomain by setting the blisache-provided activation key in the DNS TXT records for my new subdomain. Domain activation can take some time because DNS records needs to propagate. You can replace every occurence of dev.blisache.com with your own domain.

Your registered domain and the domain serving your pages must match. Also you need to serve over SSL to enable a secure context when nagivating with the browser. Your browser will most likely consider self-signed certificates as insecure, therefore you will need to generate a real certificate for your test domain. Of course when running locally you don’t really serve from a domain. We will configure an instance of Nginx as proxy to do so. Install Nginx and add this configuration to /etc/nginx/conf.d/dev.blisache.com.conf :

# /etc/nginx/conf.d/dev.blisache.com.conf

server {
    listen 443 ssl;
    server_name  dev.blisache.com; # replace with your own domain here

    ssl_certificate      /etc/letsencrypt/live/dev.blisache.com/fullchain.pem; # replace with your own cert path
    ssl_certificate_key  /etc/letsencrypt/live/dev.blisache.com/privkey.pem; # replace with your own key path

    location / {
        proxy_pass http://localhost:8888; # replace with your own port here
    }
}

Don’t forget to restart your nginx instance.

Finally you have to configure a local DNS resolution so that your test domain resolves to your local machine. This is acheived with your hosts file /etc/hosts :

# /etc/hosts

127.0.0.1 localhost dev.blisache.com

Now my app is served at the url https://dev.blisache.com and is ready to start testing and integrating the Blisache API.

Integrating the API

API is available at https://blisache.com/api/<api_endpoint>.

We provide client-side packages to help integrating the API :

Blisache-client

blisache-client is a wasm artifact exposing the API as simple javascript functions to be called. It can be imported in your project like this :

import * as blisache from "blisache-client";

Here are the prototypes of the exposed functions :

function register(login: string, name: string): Promise<any>;

register() initiates a new registration to the API, prompts the authenticator for a new key, and registers the created key to the API. Thats mean we create a new account or new user.

function authenticate(login?: string | null): Promise<any>;

authenticate() initiates a new authentication to the API, prompts the authenticator for an already registered key and authenticates the user to the API.

function credential_list(login?: string | null): Promise<any>;

credential_list() authenticates the user with authenticate() and retreives the credentials registered to the API for that user.

function credential_add(login: string, name: string): Promise<any>;

credential_add() authenticates the user with authenticate() and registers a new key for that user using register().

function credential_revoke(login?: string | null, credential_id: bigint): Promise<any>;

credential_revoke() authenticates the user with authenticate() and unregisters the user’s target credential.

The login is the account name. It is often set as the email address of the user. It is sent to the API and anonymised for storage.

The name is the name of the user. It is stored in the authenticator and used as display name. It is not sent to the API.

login and name are mandatory in registration-related endpoints.

In authentication-related endpoints login is optional. If login present, a server-side credential search is performed. When login is absent, a discoverable resident key (passkey) is selected for the ceremony with possible user prompt.

Each of these functions returns a SignedResponse. This is a data object containing a message and its associated signature. The message is the json response from the API serialized as a bytes array. The signature is the signature of the message signed from Blisache servers also serialized as a bytes array. A SignedResponse wraps success and error responses from the API and should be sent to your backend for further processing.

Blisache-html

blisache-html is a javascript package providing handlers to be hooked as html <form> submit event listener.

In the example below you register the function registerHandleSubmit as submit event listener on all elements tagged with blisacheRegister class.

import { registerHandleSubmit } from "blisache-html";

document.querySelectorAll('.blisacheRegister').forEach(form => {
  form.addEventListener("submit", registerHandleSubmit);
});

Then you define your registration form :

<form action="/register" method="post" class="blisacheRegister">
    <input type="email" name="email" data-blisache-login />
    <input type="text" name="name" data-blisache-name />
    <input type="text" name="custom_data" />
    <button type="submit">Register</button>
</form>

When this form is submited the registerHandleSubmit event handler is called. Data from the form inputs are fetched through the custom attributes data-blisache-login and data-blisache-name and are passed to the register() function from blisache-client. There is a data-blisache-credential_id custom attribute available to wire data to the credential_id field of the credential_revoke() function.

The register() function outputs message and signature as bytes arrays.

The handler then appends two new inputs to your form - blisache_message and blisache_signature - containing respectively message and signature data base64 encoded.

The form is then sent to your backend’s endpoint for processing. Notice that you can add more data to your form like the additional custom_data input. When receiving this form on server-side there will be 5 fields :

  • email
  • name
  • custom_data
  • blisache_message
  • blisache_signature

You need to verify blisache_signature against blisache_message to be sûre that data comes from blisache servers and has not been altered.

blisache_message and blisache_signature then can be base64 decoded and json deserialized according to the BlisacheResponse or BlisacheErrorResponse formats.

Server-side processing

There are few things to check when receiving SignedResponse data (blisache_message and blisache_signature) on your backend’s processing endoint.

First you want to verify blisache_message against blisache_signature to be sûre the message has not been tempered with. You can either :

Then you will have to base64 decode blisache_message to get the raw json response. That json response deserializes to either BlisacheResponse<T>, T being dependant on the called endpoint, or BlisacheErrorResponse if an error happened.

In both cases there will be some action, result and timestamp fields. You will want to check that action match your intent, that result is a success and that timestamp is not too old. The appreciation of not too old is up to you but the general idea is to protect against replay attacks.

HTTP endpoints

Registration

Authentication

Credential list, add, revoke

Register

Registration is a two-step process using the registration init and registration complete endpoints.

The provided blisache-client takes care of calling the endpoints in order.

Register init

Calling the init endpoint starts a new registration and returns data to be fed to the authenticator.

Request

MethodPathContent-typeBody
POST/register/initapplication/jsonA RegisterInitRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<RegisterInitResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Register complete

After the authenticator has generated a new credential, you need to feed back these data throught the complete endpoint.

Request

MethodPathContent-typeBody
POST/register/completeapplication/jsonA RegisterCompleteRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<RegisterCompleteResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Authenticate

Authentication is a two-step process using the authentication init and authentication complete endpoints.

The provided blisache-client takes care of calling the endpoints in order.

Authenticate init

Calling the init endpoint starts a new authentication and returns data to be fed to the authenticator.

Request

MethodPathContent-typeBody
POST/authenticate/initapplication/jsonA AuthenticateInitRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<AuthenticateInitResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Authenticate complete

After the authenticator has retreived the desired credential, you need to feed back these data throught the complete endpoint.

Request

MethodPathContent-typeBody
POST/authenticate/completeapplication/jsonA AuthenticateCompleteRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<AuthenticateCompleteResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Credential list

Listing credentials is a two-step process using the authentication init and list credentials endpoints.

Listing credentials starts like an authentication procedure by calling the authentication init endpoint but as a second step you call the list credential endpoint.

Credential list init

This is an authenticate init request.

Credential list complete

After the authenticator has retreived the desired credential, you need to feed back these data throught the complete endpoint.

Request

MethodPathContent-typeBody
POST/credential/listapplication/jsonA AuthenticateCompleteRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<CredentialListResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Credential add

Adding a credential is a three-step process using the authentication init, add credential and register complete endpoints.

Start like an authentication procedure by calling the authentication init endpoint but as a second step you call the add credential endpoint.

The challenge must be resolved using an existing credential.

The add credential endpoint returns a register init response whose challenge must be signed using the new credential to register.

Then call the registration complete endpoint to end the procedure.

Credential add init

This is an authenticate init request.

Credential add

The add credential endpoint returns a register init response whose challenge must be signed using the new credential to register. Then call the registration complete endpoint to end the procedure.

Request

MethodPathContent-typeBody
POST/credential/addapplication/jsonA AuthenticateCompleteRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<RegisterInitResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Credential add complete

This is a register complete request.

Credential revoke

Revoking credentials is a two-step process using the credential revoke init and credential revoke complete endpoints.

Credential revoke init

Calling the init endpoint starts a new revocation and returns data to be fed to the authenticator.

Request

MethodPathContent-typeBody
POST/credential/revoke/initapplication/jsonA CredentialRevokeInitRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<AuthenticateInitResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Credential revoke complete

After the authenticator has retreived the desired credential, you need to feed back these data throught the complete endpoint.

Request

MethodPathContent-typeBody
POST/credential/revoke/completeapplication/jsonA AuthenticateCompleteRequest data object

Responses

CodeContent-typeBody
200application/jsonA SignedResponse enclosing a BlisacheResponse<AuthenticateCompleteResponse>
400application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an error in the input data
422application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling a functional error
500application/jsonA SignedResponse enclosing a BlisacheErrorResponse signaling an internal server error

Signature validation

Signature validation includes an endpoint for getting the blisache public key in order to verify a signature on your own and also a convenience endpoint asking the verification of a message by the blisache backend.

Get public key

Getting the blisache public key in order to verify a signature on your own.

Request

MethodPath
GET/signature/public_key

Responses

CodeContent-typeBody
200application/jsonA PublicKeyResponse
500application/jsonA BlisacheErrorResponse signaling an internal server error

Verify signature

A convenience endpoint asking the verification of a message by the blisache backend.

Request

MethodPathContent-typeBody
POST/signature/verifyapplication/jsonA SignedResponse data object

Responses

CodeContent-typeBody
200application/jsonA SignatureVerifyResponse
400application/jsonA BlisacheErrorResponse signaling an error in the input data
422application/jsonA BlisacheErrorResponse signaling a functional error
500application/jsonA BlisacheErrorResponse signaling an internal server error

Data structures

Here are the json schemas of the data structures involved in the API.

SignedResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "SignedResponse",
  "type": "object",
  "properties": {
    "message": {
      "type": "array",
      "items": {
        "type": "integer",
        "format": "uint8",
        "maximum": 255,
        "minimum": 0
      }
    },
    "signature": {
      "type": "array",
      "items": {
        "type": "integer",
        "format": "uint8",
        "maximum": 255,
        "minimum": 0
      }
    }
  },
  "required": [
    "message",
    "signature"
  ]
}

BlisacheResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "BlisacheResponse",
  "type": "object",
  "properties": {
    "action": {
      "$ref": "#/$defs/BlisacheAction"
    },
    "data": {
      "oneOf": [
        { "$ref": "/RegisterInitResponse" },
        { "$ref": "/RegisterCompleteResponse" },
        { "$ref": "/AuthenticateInitResponse" },
        { "$ref": "/AuthenticateCompleteResponse" },
        { "$ref": "/CredentialListResponse" }
      ]
    },
    "domain": {
      "type": "string"
    },
    "result": {
      "$ref": "#/$defs/BlisacheActionResult"
    },
    "timestamp": {
      "type": "integer",
      "format": "uint64",
      "minimum": 0
    }
  },
  "required": [
    "timestamp",
    "domain",
    "action",
    "result",
    "data"
  ],
  "$defs": {
    "BlisacheAction": {
      "type": "string",
      "enum": [
        "RegisterInit",
        "RegisterComplete",
        "AuthenticateInit",
        "AuthenticateComplete",
        "ListCredentials",
        "AddCredential",
        "RevokeCredentialInit",
        "RevokeCredentialComplete",
        "GetPublicKey",
        "VerifySignature"
      ]
    },
    "BlisacheActionResult": {
      "type": "string",
      "enum": [
        "Success",
        "Error"
      ]
    }
  }
}

BlisacheErrorResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "BlisacheErrorResponse",
  "type": "object",
  "properties": {
    "action": {
      "$ref": "#/$defs/BlisacheAction"
    },
    "caused_by": {
      "$ref": "#/$defs/ErrorDescription"
    },
    "result": {
      "$ref": "#/$defs/BlisacheActionResult"
    },
    "timestamp": {
      "type": "integer",
      "format": "uint64",
      "minimum": 0
    }
  },
  "required": [
    "timestamp",
    "action",
    "result",
    "caused_by"
  ],
  "$defs": {
    "BlisacheAction": {
      "type": "string",
      "enum": [
        "RegisterInit",
        "RegisterComplete",
        "AuthenticateInit",
        "AuthenticateComplete",
        "ListCredentials",
        "AddCredential",
        "RevokeCredentialInit",
        "RevokeCredentialComplete",
        "GetPublicKey",
        "VerifySignature"
      ]
    },
    "BlisacheActionResult": {
      "type": "string",
      "enum": [
        "Success",
        "Error"
      ]
    },
    "ErrorCategory": {
      "type": "string",
      "enum": [
        "Input",
        "Functional",
        "RateLimit",
        "Technical"
      ]
    },
    "ErrorDescription": {
      "type": "object",
      "properties": {
        "category": {
          "$ref": "#/$defs/ErrorCategory"
        },
        "detail": {
          "type": "string"
        }
      },
      "required": [
        "category",
        "detail"
      ]
    }
  }
}

RegisterInitRequest

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RegisterInitRequest",
  "type": "object",
  "properties": {
    "login": {
      "type": "string"
    },
    "origin": {
      "type": "string"
    }
  },
  "required": [
    "login",
    "origin"
  ]
}

RegisterInitResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RegisterInitResponse",
  "type": "object",
  "properties": {
    "domain": {
      "type": "string"
    },
    "registration_data": {
      "$ref": "#/$defs/RegistrationData"
    },
    "user": {
      "$ref": "#/$defs/User"
    }
  },
  "required": [
    "user",
    "domain",
    "registration_data"
  ],
  "$defs": {
    "AttestationConveyancePreference": {
      "type": "string",
      "enum": [
        "None",
        "Indirect",
        "Direct",
        "Enterprise"
      ]
    },
    "AuthenticatorAttachment": {
      "type": "string",
      "enum": [
        "Any",
        "Platform",
        "CrossPlatform"
      ]
    },
    "PubKeyCredParam": {
      "type": "object",
      "properties": {
        "alg": {
          "type": "integer",
          "format": "int64"
        },
        "type": {
          "type": "string"
        }
      },
      "required": [
        "alg",
        "type"
      ]
    },
    "RegistrationData": {
      "type": "object",
      "properties": {
        "attestation_conveyance_preference": {
          "$ref": "#/$defs/AttestationConveyancePreference"
        },
        "authenticator_attachment": {
          "$ref": "#/$defs/AuthenticatorAttachment"
        },
        "challenge": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "public_key_credential_params": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/PubKeyCredParam"
          }
        },
        "resident_key": {
          "$ref": "#/$defs/ResidentKeyRequirement"
        },
        "user_id_validation_constraint": {
          "$ref": "#/$defs/UserIdValidationConstraint"
        },
        "user_verification": {
          "$ref": "#/$defs/UserVerificationRequirement"
        }
      },
      "required": [
        "id",
        "challenge",
        "authenticator_attachment",
        "resident_key",
        "user_verification",
        "attestation_conveyance_preference",
        "user_id_validation_constraint",
        "public_key_credential_params"
      ]
    },
    "ResidentKeyRequirement": {
      "type": "string",
      "enum": [
        "Discouraged",
        "Preferred",
        "Required"
      ]
    },
    "User": {
      "type": "object",
      "properties": {
        "id": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "login_hash": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        }
      },
      "required": [
        "id",
        "login_hash"
      ]
    },
    "UserIdValidationConstraint": {
      "type": "string",
      "enum": [
        "None",
        "Email"
      ]
    },
    "UserVerificationRequirement": {
      "type": "string",
      "enum": [
        "Required",
        "Preferred",
        "Discouraged"
      ]
    }
  }
}

RegisterCompleteRequest

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RegisterCompleteRequest",
  "type": "object",
  "properties": {
    "credential": {
      "$ref": "#/$defs/AttestationPublicKeyCredential"
    },
    "id": {
      "type": "integer",
      "format": "int64"
    },
    "login_hash": {
      "type": "array",
      "items": {
        "type": "integer",
        "format": "uint8",
        "maximum": 255,
        "minimum": 0
      }
    }
  },
  "required": [
    "id",
    "login_hash",
    "credential"
  ],
  "$defs": {
    "AttestationPublicKeyCredential": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "raw_id": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "response": {
          "$ref": "#/$defs/AuthenticatorAttestationResponse"
        },
        "type": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "raw_id",
        "response",
        "type"
      ]
    },
    "AuthenticatorAttestationResponse": {
      "type": "object",
      "properties": {
        "attestation_object": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "client_data_json": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "transports": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "attestation_object",
        "client_data_json",
        "transports"
      ]
    }
  }
}

RegisterCompleteResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "RegisterCompleteResponse",
  "type": "object",
  "properties": {
    "user_id": {
      "type": "integer",
      "format": "int64"
    },
    "user_login_hash": {
      "type": "array",
      "items": {
        "type": "integer",
        "format": "uint8",
        "maximum": 255,
        "minimum": 0
      }
    }
  },
  "required": [
    "user_login_hash",
    "user_id"
  ]
}

AuthenticateInitRequest

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AuthenticateInitRequest",
  "type": "object",
  "properties": {
    "login": {
      "type": [
        "string",
        "null"
      ]
    },
    "origin": {
      "type": "string"
    }
  },
  "required": [
    "origin"
  ]
}

AuthenticateInitResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AuthenticateInitResponse",
  "type": "object",
  "properties": {
    "authentication_data": {
      "$ref": "#/$defs/AuthenticationData"
    },
    "domain": {
      "type": "string"
    },
    "user_login_hash": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "integer",
        "format": "uint8",
        "maximum": 255,
        "minimum": 0
      }
    }
  },
  "required": [
    "domain",
    "authentication_data"
  ],
  "$defs": {
    "AuthenticateAllowCredential": {
      "type": "object",
      "properties": {
        "id": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "transports": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "type": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "transports",
        "type"
      ]
    },
    "AuthenticationData": {
      "type": "object",
      "properties": {
        "allowed_credentials": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "$ref": "#/$defs/AuthenticateAllowCredential"
          }
        },
        "challenge": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "user_verification": {
          "$ref": "#/$defs/UserVerificationRequirement"
        }
      },
      "required": [
        "id",
        "challenge",
        "user_verification"
      ]
    },
    "UserVerificationRequirement": {
      "type": "string",
      "enum": [
        "Required",
        "Preferred",
        "Discouraged"
      ]
    }
  }
}

AuthenticateCompleteRequest

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AuthenticateCompleteRequest",
  "type": "object",
  "properties": {
    "credential": {
      "$ref": "#/$defs/AuthenticationPublicKeyCredential"
    },
    "id": {
      "type": "integer",
      "format": "int64"
    },
    "login_hash": {
      "type": [
        "array",
        "null"
      ],
      "items": {
        "type": "integer",
        "format": "uint8",
        "maximum": 255,
        "minimum": 0
      }
    }
  },
  "required": [
    "id",
    "credential"
  ],
  "$defs": {
    "AuthenticationPublicKeyCredential": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string"
        },
        "raw_id": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "response": {
          "$ref": "#/$defs/AuthenticatorAssertionResponse"
        },
        "type": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "raw_id",
        "response",
        "type"
      ]
    },
    "AuthenticatorAssertionResponse": {
      "type": "object",
      "properties": {
        "authenticator_data": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "client_data_json": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "signature": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "user_handle": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        }
      },
      "required": [
        "authenticator_data",
        "client_data_json",
        "signature"
      ]
    }
  }
}

AuthenticateCompleteResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "AuthenticateCompleteResponse",
  "type": "object",
  "properties": {
    "sign_count_warning": {
      "type": "boolean"
    },
    "user_id": {
      "type": "integer",
      "format": "int64"
    }
  },
  "required": [
    "user_id",
    "sign_count_warning"
  ]
}

CredentialListResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "CredentialListResponse",
  "type": "object",
  "properties": {
    "keys": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Credential"
      }
    },
    "user_id": {
      "type": "integer",
      "format": "int64"
    }
  },
  "required": [
    "user_id",
    "keys"
  ],
  "$defs": {
    "Credential": {
      "type": "object",
      "properties": {
        "backup_eligibility": {
          "type": "boolean"
        },
        "backup_state": {
          "type": "boolean"
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        },
        "credential_type": {
          "type": "string"
        },
        "fingerprint": {
          "type": "array",
          "items": {
            "type": "integer",
            "format": "uint8",
            "maximum": 255,
            "minimum": 0
          }
        },
        "id": {
          "type": "integer",
          "format": "int64"
        },
        "last_used_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "sign_count": {
          "type": "integer",
          "format": "int64"
        },
        "transports": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "user_verified": {
          "type": "boolean"
        }
      },
      "required": [
        "id",
        "fingerprint",
        "credential_type",
        "sign_count",
        "user_verified",
        "transports",
        "backup_eligibility",
        "backup_state",
        "created_at"
      ]
    }
  }
}

CredentialRevokeInitRequest

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "CredentialRevokeInitRequest",
  "type": "object",
  "properties": {
    "credential_id": {
      "type": "integer",
      "format": "int64"
    },
    "login": {
      "type": [
        "string",
        "null"
      ]
    },
    "origin": {
      "type": "string"
    }
  },
  "required": [
    "credential_id",
    "origin"
  ]
}

PublicKeyResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "PublicKeyResponse",
  "type": "array",
  "items": {
    "$ref": "#/$defs/PublicKeyResponseData"
  },
  "$defs": {
    "PublicKeyResponseData": {
      "type": "object",
      "properties": {
        "alg": {
          "$ref": "#/$defs/SignatureAlgorithm"
        },
        "pem": {
          "type": "string"
        }
      },
      "required": [
        "pem",
        "alg"
      ]
    },
    "SignatureAlgorithm": {
      "type": "string",
      "enum": [
        "Ed25519"
      ]
    }
  }
}

SignatureVerifyResponse

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "SignatureVerifyResponse",
  "type": "object",
  "properties": {
    "result": {
      "$ref": "#/$defs/BlisacheActionResult"
    }
  },
  "required": [
    "result"
  ],
  "$defs": {
    "BlisacheActionResult": {
      "type": "string",
      "enum": [
        "Success",
        "Error"
      ]
    }
  }
}