Skip to content

Commit a976953

Browse files
authored
chore: add TOTP, passkey, and WebAuthn credential import documentation (#2512)
* chore: add TOTP, passkey, and WebAuthn credential import documentation * chore: update documentation to include WebAuthn in import support list
1 parent 5b13fc6 commit a976953

1 file changed

Lines changed: 133 additions & 1 deletion

File tree

docs/kratos/manage-identities/25_import-user-accounts-identities.mdx

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ recovery read the [account recovery documentation](../self-service/flows/account
129129

130130
## Importing credentials
131131

132-
Ory supports importing credentials for identities including passwords and social sign-in connections.
132+
Ory supports importing credentials for identities including passwords, social sign-in connections, TOTP, WebAuthn, and passkeys.
133133

134134
### Clear text password
135135

@@ -787,6 +787,138 @@ must be the ID of the user on the given platform. The subject should not be an e
787787
}
788788
```
789789
790+
### TOTP credentials
791+
792+
To import an identity with an existing TOTP (time-based one-time password) credential, provide the TOTP secret as an `otpauth://`
793+
URL following the [Google Authenticator Key URI format](https://github.com/google/google-authenticator/wiki/Key-Uri-Format).
794+
795+
This allows users to continue using their existing authenticator app without re-enrollment.
796+
797+
```json {6-12}
798+
{
799+
"schema_id": "preset://email",
800+
"traits": {
801+
"email": "docs-totp@example.org"
802+
},
803+
"credentials": {
804+
"totp": {
805+
"config": {
806+
"totp_url": "otpauth://totp/MyApp:docs-totp@example.org?secret=JBSWY3DPEHPK3PXP&issuer=MyApp"
807+
}
808+
}
809+
}
810+
}
811+
```
812+
813+
The `totp_url` must be a valid `otpauth://` URL. The URL contains the shared secret, issuer, and account name:
814+
815+
```
816+
otpauth://totp/{Issuer}:{AccountName}?secret={SECRET}&issuer={Issuer}
817+
```
818+
819+
- `secret` (required): Base32-encoded shared secret.
820+
- `issuer` (required): Service or company name.
821+
- `digits` (optional): Number of digits, default is 6.
822+
- `period` (optional): Time step in seconds, default is 30.
823+
- `algorithm` (optional): Hash algorithm (`SHA1`, `SHA256`, `SHA512`), default is `SHA1`.
824+
825+
Each identity supports one TOTP credential. Importing a new TOTP credential replaces the previous one.
826+
827+
### Passkey credentials
828+
829+
To import an identity with existing passkey credentials, provide the WebAuthn credential data including the credential ID, public
830+
key, and authenticator metadata. All binary fields must be base64-encoded.
831+
832+
```json {6-24}
833+
{
834+
"schema_id": "preset://email",
835+
"traits": {
836+
"email": "docs-passkey@example.org"
837+
},
838+
"credentials": {
839+
"passkey": {
840+
"config": {
841+
"user_handle": "dXNlci1oYW5kbGUtMQ==",
842+
"credentials": [
843+
{
844+
"id": "Y3JlZGVudGlhbC1pZC0x",
845+
"public_key": "cHVibGljLWtleS0x",
846+
"attestation_type": "none",
847+
"display_name": "Work Laptop",
848+
"is_passwordless": true,
849+
"authenticator": {
850+
"aaguid": "YWFndWlkLTE=",
851+
"sign_count": 0,
852+
"clone_warning": false
853+
}
854+
}
855+
]
856+
}
857+
}
858+
}
859+
}
860+
```
861+
862+
Each credential requires the following fields:
863+
864+
- `id` (base64, required): The credential ID assigned during registration.
865+
- `public_key` (base64, required): The COSE-encoded public key from the authenticator.
866+
- `attestation_type` (string, required): The attestation type. Use `"none"` if the original attestation is not available.
867+
- `authenticator.aaguid` (base64, required): The Authenticator Attestation GUID identifying the authenticator model.
868+
- `authenticator.sign_count` (integer, required): The signature counter from the last authentication.
869+
- `authenticator.clone_warning` (boolean, required): Set to `true` if the credential may have been cloned.
870+
- `user_handle` (base64, required on create): The WebAuthn user handle. Must be unique per identity.
871+
872+
Optional fields include `display_name`, `is_passwordless`, `added_at`, `flags` (`user_present`, `user_verified`,
873+
`backup_eligible`, `backup_state`), and `transport` (`"usb"`, `"nfc"`, `"ble"`, `"internal"`).
874+
875+
An identity can have multiple passkey credentials. When updating an identity, existing credentials with the same `id` are updated
876+
in place and new credentials are appended.
877+
878+
:::warning
879+
880+
Passkeys are bound to a relying party (RP) ID, typically your domain. If the RP ID changes during migration, existing passkeys
881+
will not work. Make sure the RP ID in your Ory configuration matches the one used when the passkeys were originally registered.
882+
883+
:::
884+
885+
### WebAuthn credentials
886+
887+
WebAuthn credentials used for second-factor authentication (FIDO2 security keys) use the same format as passkeys. Use the
888+
`webauthn` key instead of `passkey`:
889+
890+
```json {6-24}
891+
{
892+
"schema_id": "preset://email",
893+
"traits": {
894+
"email": "docs-webauthn@example.org"
895+
},
896+
"credentials": {
897+
"webauthn": {
898+
"config": {
899+
"user_handle": "dXNlci1oYW5kbGUtMQ==",
900+
"credentials": [
901+
{
902+
"id": "Y3JlZGVudGlhbC1pZC0x",
903+
"public_key": "cHVibGljLWtleS0x",
904+
"attestation_type": "none",
905+
"display_name": "YubiKey",
906+
"is_passwordless": false,
907+
"authenticator": {
908+
"aaguid": "YWFndWlkLTE=",
909+
"sign_count": 10,
910+
"clone_warning": false
911+
}
912+
}
913+
]
914+
}
915+
}
916+
}
917+
}
918+
```
919+
920+
Set `is_passwordless` to `false` for second-factor WebAuthn credentials and `true` for passkeys.
921+
790922
## Organization-specific SAML and OIDC connections
791923
792924
When importing SAML or OIDC connections that are only available for certain [organizations](../organizations/organizations.mdx)

0 commit comments

Comments
 (0)