This project serves as a reference to implementing the WebAuthn protocol as a Python flask backend. It's a simple api that can be replicated to another language/framework following basic RESTful APIs structure.
The core logic resides within services directory, the rest is just simple layering to expose the business logic to the users.
- Relying party: Your backend
- Authenticator: Either a software on a supported device (ex: windows machine with Windows Hello enabled and a Trusted Platform Module hardware chip) which manages its user's passkeys and passwords (i.e: Google Password Manager, Windows Hello).
- Client: A user's browser or native app initiating the process.
The project's code was built using the following packages:
The CredentialEntity
class represents the passkey credentials proper to the user who initiated the WebAuthn flow on your backend.
You have to save this entity as separate table/document to have a per device setup OR integrate its fields into your User entity.
The class is used to store and verify the allowed/excluded credentials that the user can use within your system.
from pydantic import BaseModel, Field
class CredentialEntity(BaseModel):
id: str = Field(default="")
user_id: str = Field(default="")
credential_id: str = Field(default="")
credential_public_key: str = Field(default="")
sign_count: int = Field(default=0)
credential_device_type: str = Field(default="")
credential_backed_up: bool = Field(default=False)
transports: list[str] = Field(default_factory=list)
This is the DTO (data transfer object) you MUST use to facilitate the transfer of keys and/or data between your frontend (specifically the passkey authenticator) and your relying party.
It's composed of different classes to make it easier to just pass around as dictionaries or "STRINGIFY" it for convenience.
from pydantic import BaseModel, Field
class PKResponse(BaseModel):
attestationObject: str = Field(default="")
clientDataJSON: str = Field(default="")
transports: list[str] = Field(default_factory=list)
authenticatorData: str = Field(default="")
signature: str = Field(default="")
userHandle: str = Field(default="")
class PKCredential(BaseModel):
id: str = Field(default="")
rawId: str = Field(default="")
response: PKResponse
type: str = Field(default="")
clientExtensionResults: dict
authenticatorAttachment: str = Field(default="")
class PasskeyVerifModel(BaseModel):
user_id: str = Field(default="") # You may want to ommit the user_id from here and extract it from the incoming request after the user has been signed in.
credential: PKCredential
expected_challenge: str = Field(default="")
This project is licensed under the MIT permissible license, see license for more info.