Better Auth is a pluggable authentication protocol, agnostic to platform, encoding, cryptographic and storage choices. Designed to be accessible, flexible and secure.
Examples given are encoded using CESR (Composable Event Streaming Representation) and ideas about forward commitment came from KERI (Key Event Receipt Infrastructure).
The examples were generated by the reference typescript implementation which is a great place to learn more about how this works.
Another excellent resource is this example. It contains a functional kubernetes deployment and iOS app. There's a youtube video which helps one understand how it is exercised.
I talk about CAs in the [unscripted] video and should probably clarify that the difference between the key chain and a CA root is that the key chain has a persistent identity, it's not just a key in a certificate that needs to be tracked and managed. The management is in the data of the keychain documents themselves. They have many properties we are interested in. To name a few:
- Self-Addressing Identification (deriving ids from data they represent and embedding those identifiers in that data)
- Self-Certification (each document is signed by a key embedded in it)
- Forward Commitment (each document makes a commitment to the next key without revealing it, and that key is used to sign the next document)
- Chaining (each document refers to the id of the previous document)
For the first, self-addressing identifiers provide many benefits:
| Attack / Protection Type | CA Certificate Protects? | Self-Addressing Identifier Protects? |
|---|---|---|
| Tampering (integrity violation) | Yes | Yes |
| Substitution (fake data) | Partially | Yes |
| Replay/Rebinding (ID assigned to different data) | No | Yes |
| Ambiguity/Collisions (multiple IDs for similar data) | No | Yes |
| Secure, immutable referencing | No | Yes |
| Impersonation of issuer/subject | Yes | Yes |
For the third, forward commitment provides:
| Property / Attack Protection | CA Certificate Protects? | Forward Commitment Protects? |
|---|---|---|
| Hiding value until revealed | No | Yes |
| Binding value to commitment (unchangeable) | Yes | Yes |
| Prevention of early exposure of committed value | No | Yes |
| Proof that value existed at commitment time | No | Yes |
| Protection against equivocation/replay | Partially | Yes |
| Commitment to a future key/public value | No | Yes |
| Secure delayed or conditional reveal | No | Yes |
Disclaimer: AI made those tables for me. I have an issue with how it says a CA binds the value and commitment, because we are talking specifically about a forward commitment and a CA does not make one. So, it does bind to the current values, but there is no data level guarantee an adversary doesn't somehow rotate into another key they control.
I wanted to create a re-usable, secure polyglot framework for building ecosystems of software and this seemed like a logical starting point.
The k8s example in the examples directory gives a better idea of what I was actually trying to create. During this process I wanted to see what was necessary to provide secure documentation surrounding key management (this is the foundation of a decentralized system). The k8s example, as it exists now, isn't decentralized, but it's built in a similar manner. I found it actually started becoming constraining not to have decentralized tooling available as the security problems became more complex, and I ended up building a lot of it. The structure I use to secure the HSM key chain is essentially a lightweight KERI KEL.
To clone this repository with all implementation submodules:
git clone --recurse-submodules [email protected]:jasoncolburne/better-auth.git
cd better-authIf you've already cloned without --recurse-submodules, initialize the submodules:
git submodule update --init --recursiveTo pull the latest changes from all submodules:
./scripts/pull-repos.shTo set up all implementation repositories (install dependencies):
./scripts/run-setup.shThis will run make setup in each implementation repository, which:
- Installs npm packages (TypeScript)
- Creates a Python venv and installs dependencies (Python)
- Downloads Go modules (Go)
- Installs Ruby gems (Ruby)
- Fetches Rust crates (Rust)
- Resolves Swift packages (Swift)
- Gets Dart packages (Dart)
- Sets up Gradle dependencies (Kotlin)
Some implementations will be gracefully skipped if tooling is unavailable.
This repository includes a pre-commit hook that ensures submodules are on the main branch before committing to the parent repository. The hook blocks commits if any submodule is on a feature branch or in detached HEAD state. This is critical because:
- Git submodules record specific commit hashes
- Committing while a submodule is on a feature branch or detached HEAD can break reproducible builds
- Other developers may not be able to access those commits if they're unreachable or on unpushed branches
The detached HEAD constraint may be revisited when the implementations are published.
The hooks are automatically installed when you run ./scripts/run-setup.sh.
To manually install hooks:
./scripts/install-hooks.shIf you need to override the hook (not recommended):
git commit --no-verify -m "message"Each implementation is a git submodule located in implementations/. Here's the recommended workflow:
1. Work on feature branches in submodules:
cd implementations/better-auth-ts
git checkout -b feature/my-feature
# make changes, commit2. When ready, merge to main in the submodule:
git checkout main
git merge feature/my-feature
git push origin main3. Then commit to the parent repository:
cd ../..
git add implementations/better-auth-ts
git commit -m "Update better-auth-ts: my feature"The pre-commit hook will block step 3 if you forget step 2, preventing accidental feature branch or detached HEAD commits from being recorded in the parent repo.
All orchestration scripts are in the scripts/ directory:
./scripts/run-setup.sh # Setup all implementations + install hooks
./scripts/pull-repos.sh # Pull latest changes (only if on main)
./scripts/run-type-checks.sh # Run type checkers
./scripts/run-unit-tests.sh # Run unit tests
./scripts/run-lints.sh # Run linters
./scripts/run-format-checks.sh # Check code formatting
./scripts/run-integration-tests.sh # Run integration tests
./scripts/run-all-checks.sh  # Run all checks in sequenceEach implementation has standardized Makefile targets:
make setup- Install dependenciesmake test- Run unit testsmake type-check- Run type checkermake lint- Run lintermake format- Auto-format codemake format-check- Check formattingmake clean- Clean build artifacts
Check examples/garden-k8s for a multi-service Kubernetes deployment. Follow instructions in
examples/garden-k8s/README.md and then run npm run test:k8s from the typescript implementation.
Additionally, there is also an iOS app that exhibits the functionality of these protocols. Start two simulators against the k8s deployment, and you can perform linking, unlinking and recovery - as well as the independent operations.
- All actions except authentication to acquire an access session are gated by a rotation operation.
- All requests contain a nonce to challenge the server.
- Authentication is a two phase operation that involves the server challenging the client.
- Rotations are protected using forward commitment.
This has yet to be formally reviewed, and the claude implementations have barely been reviewed by me. I got them working and plan to come back for polish.
Client implementations:
Server implementations:
In each protocol request or response payload one will find some data contexts:
access: This will contain information relevant to the request and response pair.request: This is the request.response: This is the response.
The request and response contexts are broken down further into more contexts:
access: This will contain information that permits access by the current device.authentication: This context will contain information that permits authentication of the current device.linkThis contains information about another device. If necessary, this is broken into contexts as well (when creating a link, the link context contains a payload and signature from the device being linked).
There are 4 groups of protocols. Account, Device, Session, and Recovery. Recovery only
contains a method to update an identity's recovery key hash. The RecoverAccount() method exists
in the Account protocol group.
Additionally, there is an Access request protocol that supports arbitrary request/response
payloads.
To start, we need a recovery public key as an input. Then, we use a client interface to generate two more keypairs and an identity. One key pair to reveal immediately and use for authentication, and the other to reveal in the future when rotating. The identity derivation is up to the implementer, but could conceivably go as far as a DKMS like KERI. The verification of this identity is performed in another server side interface, before returning a response.
device is simply a hash digest of the publicKey and rotationHash, and the protocol handles
this transformation and verification.
request
{
"payload": {
"access": {
"nonce": "0ABic13dCJIYixhIS8fd6kfC"
},
"request": {
"authentication": {
"device": "EOnMhfF6CIKCvXrZkRxwPMBRy6MwgwSBM0H6hb1uDezu",
"identity": "EDuDnuc2x21LfxlPQvvKSQoaOqOCMpoi4bbuX7DlsIEg",
"publicKey": "1AAIAkZeridwme6y4GpivAoI9sw5LNyj9BJD5USSAJu165AD",
"recoveryHash": "EBjQipjCHv-6_Gfr5SlMHsAajVJehBlgbqKz48wepiDI",
"rotationHash": "EExjdqXJ8YEur1h_28-0SANF1dRnw3MpeCRZI--oR8Ou"
}
}
},
"signature": "0ID6mIMIBB9CGGygwW8rkAow4J7BgDKALJ-v2A86EmeicR7P304fcLEfRNcu_XI0oCmS-lSDUlFyKFzy9WY29EEY"
}The server interfaces should check for uniqueness of identity and ensure it does not exist, which
is important because the client controls creation. Using the established methods, device and
identity should be verified. The recoveryHash should next be registered in storage with a
lookup key of identity. Finally the publicKey and rotationHash should be stored against a key
that looks like (identity, device). This ordering ensures an account is never available for
authentication until a recoveryHash has been established.
response
{
"payload": {
"access": {
"nonce": "0ABic13dCJIYixhIS8fd6kfC",
"serverIdentity": "1AAIA3gwJej58j_uVqUln-CjkaRihnQophMChhFNq_6bBvRE"
},
"response": {}
},
"signature": "0IDfojvyFkTvGumK2bfzcb7Lv3NcXfo1DFn2yqpE8pXyOjXK9XT5zq6J0lUX5nRDnIjJt0Hg-E7I7VI4SiAzXWJI"
}Delete account removes the record of the account on the server, and also removes associated devices.
request
{
"payload": {
"access": {
"nonce": "0AA29lw2GfElc_vN2nZBY-KO"
},
"request": {
"authentication": {
"device": "EHjNZBQHfL46WumdUPr1MMSSdX2f1s8FRHy_wvax1p0X",
"identity": "EFPS0fUY7gHy-R4N9yfzfdqZKQnSOl15hutYJVuVqUzn",
"publicKey": "1AAIA1WNz7MEhI1G1cEkG5cWbtIqCub6v0ip06ZLflKpcto5",
"rotationHash": "EDj7jwdHxVDMSg2JcPTZzg_f_tNWbvH9uDcZhYwXacM2"
}
}
},
"signature": "0IAx6sp9SPN4IRPm-oEmRewPN6XAeDP0gYk0WkvPXmdfB2xDwtKvSaAuaiXBawLJ1QjPWzUf-zs0AUGWeGgrbYUH"
}response
{
"payload": {
"access": {
"nonce": "0AA29lw2GfElc_vN2nZBY-KO",
"serverIdentity": "1AAIA1g5v_ZwgpR8jaQrti05grWVKCccnANyOz156q9-mr-L"
},
"response": {}
},
"signature": "0IAs58qlfNIV8hQIMR8DKs9PFTo90cDnirnvD_03RQQtAH7mJi3tFzcSo2DVj7oroPo4FnT-DRKu_dytd2bJ5sF7"
}We registered a recoveryHash on account creation. Here, we pass the recoveryKey to the server
and sign with it, sending details to establish a new device against an existing identity in the
backend. It also revokes access to all existing devices. We establish a new recovery key when we use
the existing one.
request
{
"payload": {
"access": {
"nonce": "0AAhWVyXwhyY7Nk8oGLFdIPv"
},
"request": {
"authentication": {
"device": "EIcNq7KeNz54g9bJbYL87VK83YSzNUXXKfLZMmMEBQb2",
"identity": "EJ_0GWDWEO5_147xvTIIR94MSalYQ_haXg0_MbGTFaBI",
"publicKey": "1AAIAh2TQRHwjc3AnkH92s1lSRrujfDfOI8SXs8rpb26hDzv",
"recoveryHash": "ECbnTNMWa4eJBx_RZdetPWh4QJ1lCEfz4_3_Pj3u-8ZM",
"recoveryKey": "1AAIAqMfP4eY4TzVtK7gWYbS6G7m4RW23uLSDq_OLwFlTjlV",
"rotationHash": "ELMgW2yWYFUjKXFiFPBZuXaYw1vyk8rTDHWf4ZZXtyon"
}
}
},
"signature": "0IABMd20fxa5rCscWJG5UB_gi3s3VAoqVGqqfzOunTFy5vVjlp16r2BUurI_r8pMvMjuUsu8oZjmXd_g7Uh_Z7Vb"
}The server verifies the key by looking up recoveryHash and recomputing. If the hash matches, the
signature is verified using recoveryKey. device should be checked for uniqueness. If all is
well, the backend registers publicKey and rotationHash against a key that looks like
(identity, device).
response
{
"payload": {
"access": {
"nonce": "0AAhWVyXwhyY7Nk8oGLFdIPv",
"serverIdentity": "1AAIAqIT42GJw-M5tCuE0_9zVUBIOTgSlBoVsPGgx_i5p0lr"
},
"response": {}
},
"signature": "0ICjKpJ5F2iX-zq4k_S2K0tGGV8tI3INg-d87pYFctcaus9avVpRtaEQsQC8NEOVv9ad7bkJaU8rxU7t-ry6obZ4"
}This is a link container, it is generated by the new device, and contains enough data to link a device to an existing identity.
{
"payload": {
"authentication": {
"device": "EM9MnUABj7vcjZVkxaUGp3avVekn95sbJTzfF5_VLLNI",
"identity": "EBORvlvmBkZvRNXHQ0gF5nuqEwoPW5TH6cpahDpp4bjM",
"publicKey": "1AAIAnsOjRzzHpxfxbiL2vMoXCvoSqiJiE-Grkv_EgKyrZ5V",
"rotationHash": "EDBdHflCJPkR7RUb918q6gpnZQCtCSbTwk6zL1vBmpxt"
}
},
"signature": "0IA34K3h0LtmblC2X9qT57vUq2XrQrEoJp_HgLHN0FwNR2vGwQph__uxsl9ichML9NmdwIfBmMXdv3AV3jtTpjOV"
}The container is embedded in a message created and signed by the existing device. A rotation is performed by the existing device.
request
{
"payload": {
"access": {
"nonce": "0ACfg5r4dCDg1SUCGCH9BaFK"
},
"request": {
"authentication": {
"device": "EKd76BaGOObJTIcGFGX6ql0IW05DESgYX5nbNjnTlNUH",
"identity": "EBORvlvmBkZvRNXHQ0gF5nuqEwoPW5TH6cpahDpp4bjM",
"publicKey": "1AAIAjzuMzAhD3hibZDbX0WWv315iCqRePbBEjUuk14thr26",
"rotationHash": "EBtlgdPYcmvsJ6KQr46KoGbbqgukese-HL6yaelZj_rt"
},
"link": {
"payload": {
"authentication": {
"device": "EM9MnUABj7vcjZVkxaUGp3avVekn95sbJTzfF5_VLLNI",
"identity": "EBORvlvmBkZvRNXHQ0gF5nuqEwoPW5TH6cpahDpp4bjM",
"publicKey": "1AAIAnsOjRzzHpxfxbiL2vMoXCvoSqiJiE-Grkv_EgKyrZ5V",
"rotationHash": "EDBdHflCJPkR7RUb918q6gpnZQCtCSbTwk6zL1vBmpxt"
}
},
"signature": "0IA34K3h0LtmblC2X9qT57vUq2XrQrEoJp_HgLHN0FwNR2vGwQph__uxsl9ichML9NmdwIfBmMXdv3AV3jtTpjOV"
}
}
},
"signature": "0IARmgp45duSRHEw59PdubfC0Flwk2IJGKIIv7vFVEoax3ByPYaPmEm85q3x-zWNz9nYU7xQTj0hp1PtYnmqjjuH"
}After verifying the authentication signature from the existing device (via the authentication
part of the request), the link object is parsed and verified, and a new set of device keys is
registered in storage.
response
{
"payload": {
"access": {
"nonce": "0ACfg5r4dCDg1SUCGCH9BaFK",
"serverIdentity": "1AAIAjvyqhtStWKRaRVjZ4PtBvgIW8aiYX4K5eQEh-xud3Vx"
},
"response": {}
},
"signature": "0IBHeDkykYM_alvHVpC5gJYOIMDNMS3_3Hg2rygoMfVYAvzlwHVg-Z_uXFLqLhbB8MhfUR06FowfT1jrG8yWTRtV"
}Unlinking involves a rotation followed by revoking access to the linked device. If the device is the same device that is authenticating via rotation, we also double hash the rotation hash so that a future rotation is not possible while simplifying backend logic.
{
"payload": {
"access": {
"nonce": "0ADFPjfZ_QQiRPVWH3vvNn_-"
},
"request": {
"authentication": {
"device": "EM9MnUABj7vcjZVkxaUGp3avVekn95sbJTzfF5_VLLNI",
"identity": "EBORvlvmBkZvRNXHQ0gF5nuqEwoPW5TH6cpahDpp4bjM",
"publicKey": "1AAIAznaMF_aVWPXZi83Y3PKwsf8mGnQym1EL8-AdGEuoWGr",
"rotationHash": "EOBxWvzXT4mci_htA21-C2g5Yw924SN_SqQNAuDX-TZZ"
},
"link": {
"device": "EKd76BaGOObJTIcGFGX6ql0IW05DESgYX5nbNjnTlNUH"
}
}
},
"signature": "0IAVkiNVcioJFNoM5bUFf3SNFKcB7tUT5zEaplv2JwMHSoMxnD082SAj7GO4yrHc3umVVkhAvZ1HEPsks4ydV2gx"
}{
"payload": {
"access": {
"nonce": "0ADFPjfZ_QQiRPVWH3vvNn_-",
"serverIdentity": "1AAIAjvyqhtStWKRaRVjZ4PtBvgIW8aiYX4K5eQEh-xud3Vx"
},
"response": {}
},
"signature": "0ICzVKNgPSulMItYqyiPqRdYemH_r6A6nwLecYi5l-oTHF3jLykQeVz2wBaFlaYuHJdBmu2YDbnD8FoB3KOQPkf4"
}To rotate to a new key, the device reveals the hidden rotation key, turns it into the public key for that device, and establishes a new rotation key using a hash, signing with the newly-revealed key.
request
{
"payload": {
"access": {
"nonce": "0AD-6VwXbCX8cvRIdwaRrGvZ"
},
"request": {
"authentication": {
"device": "EOnMhfF6CIKCvXrZkRxwPMBRy6MwgwSBM0H6hb1uDezu",
"identity": "EDuDnuc2x21LfxlPQvvKSQoaOqOCMpoi4bbuX7DlsIEg",
"publicKey": "1AAIAtyDmFoPNHBnvd_ABDDmRqSWPjLG44UJXX-vb9-fYZkX",
"rotationHash": "EFMfoXB0rwozYH7E5PIr_-k1ur6d3rR2oQcCiOq6f6-j"
}
}
},
"signature": "0IDxX3fdfoIouzhhdHFLGUYH3Vg7nntIl0WZbbewZyJT5CS_O2KqJLFM4J2OBroYA6HKAay2Fa9A533bdTTR3PCm"
}response
{
"payload": {
"access": {
"nonce": "0AD-6VwXbCX8cvRIdwaRrGvZ",
"serverIdentity": "1AAIA3gwJej58j_uVqUln-CjkaRihnQophMChhFNq_6bBvRE"
},
"response": {}
},
"signature": "0IAnQ9Q2H88Jx_Y-U_6ZmE38gdE6boVKJXjCcORb-v-Q7Ujs1CCJ4kfBtsxsntjztfTPT0D8J23SrrJ_AqIu179A"
}Authentication is accomplished with a challenge/response performed in two phases.
request (request for challenge)
{
"payload": {
"access": {
"nonce": "0ADIkSgmBYYofVeJb89qiUlg"
},
"request": {
"authentication": {
"identity": "EKtSY4qSvCBBKQJaPLL5ir1Gewwim3VDmgLHyaiXuDbh"
}
}
}
}response (challenge)
{
"payload": {
"access": {
"nonce": "0ADIkSgmBYYofVeJb89qiUlg",
"serverIdentity": "1AAIA68_K08yASZus-UFGqzXwORIMQqP9581WUypmElmLZ8d"
},
"response": {
"authentication": {
"nonce": "0ADM67kQpki2QBtmyaONjcrg"
}
}
},
"signature": "0IDa0ixqGi4ps8594x0qKmv2D0GPlkFjEci9ESsm0BvOkKC4nPFWGd1Vyr8ILri1hwLi2c02qwmUrQ2D1XyXExE2"
}request (response to challenge)
{
"payload": {
"access": {
"nonce": "0ACAsJ-EephNBsbhGk4tUZ_B"
},
"request": {
"access": {
"publicKey": "1AAIA1mfw2FyjMjJ35KQ4AHoEsvl3rNL4lLpRaTO1QqmkIap",
"rotationHash": "EAhM6XuAsBHzZPDz0oXWJEx__AphCZwCIesHoiMnEicU"
},
"authentication": {
"device": "EK6GaKFuQJPTdKWzTEbCAJDpT31aRVX5boKPgNY7YXCK",
"nonce": "0ADM67kQpki2QBtmyaONjcrg"
}
}
},
"signature": "0IA0KaZTltOnpZF13KcpdgPfI40G82EOzEsvqjDv4m69NowcF75o3DdMQGCJNIgopfkWTBPlecgqoimdW73xkfoB"
}response (grant)
{
"payload": {
"access": {
"nonce": "0ACAsJ-EephNBsbhGk4tUZ_B",
"serverIdentity": "1AAIA68_K08yASZus-UFGqzXwORIMQqP9581WUypmElmLZ8d"
},
"response": {
"access": {
"token": "0IAdE2pfsEYLNcsSLCm7gbhqMyWvr4sucf0joi3s_T95jUtEbigI5ywksdgRJNbZt7iucO2gCTlmNhUSOrb-uC0-H4sIAAAAAAACA2WPXXOiMBiF_0uu2x0-dctdRBQIdsHSFul0OkGCxAqhSRCx43_feLPt7t6-8zznnPcTCMKPhAclaSWVI3CADmEAW1F2P_dcnlJNw2FQxSyvJo_VLsmJyWBa55sTtYZlkixscANKcqRbolwPTZYYLfokjNMSPZ9Tr3BhOO9SU8frp8wuGIp395vpJnOR8uhXq4fkw8b6eDi6sxlKQhxHkU25viTDQBvzad7sIn_ENOvnRa3Mri8OdIvIn8F6Uw3GYtyv9qFpo8SCPvPE8WDy-8g6RN0ap7_05KN5D3CndM4klpS1Phb1tRzWq0nWQzHzz3k8P2ssew6909sb7Go3H9yACJ_RVevR7eN1thA9KaFUpqEZ9q2u3ep3qT51jImjTX9od0auKHLqKB__Yyz9G8NJxYmovX9RQ0s1--84LCWnRS-JAM4n6Ahv1Az1gpiNa3Yg1yMuG9oC50XF4lIpA6eSgNfL5fIbAzTuRucBAAA"
}
}
},
"signature": "0ICeOLo0xv308DzI_VFEH64mGgRsjMs75yA2RcN4sKKSd9NJUPCmdQMOkvVAlNWcCKOMNqotCQ1U2HCpaByFz0ZS"
}This rotates the access key.
request
{
"payload": {
"access": {
"nonce": "0ADWlMMYKbaPZcPNd9C73Ny_"
},
"request": {
"access": {
"publicKey": "1AAIAxwArqK3Bo3xiltNj5wqvs5MK7E7e5ZqoE_5f-oFm-ZX",
"rotationHash": "EOu0Xxx5XaOovLEPsi-aibP1s1vnUC-HnEJLb5gD_Hay",
"token": "0IAdE2pfsEYLNcsSLCm7gbhqMyWvr4sucf0joi3s_T95jUtEbigI5ywksdgRJNbZt7iucO2gCTlmNhUSOrb-uC0-H4sIAAAAAAACA2WPXXOiMBiF_0uu2x0-dctdRBQIdsHSFul0OkGCxAqhSRCx43_feLPt7t6-8zznnPcTCMKPhAclaSWVI3CADmEAW1F2P_dcnlJNw2FQxSyvJo_VLsmJyWBa55sTtYZlkixscANKcqRbolwPTZYYLfokjNMSPZ9Tr3BhOO9SU8frp8wuGIp395vpJnOR8uhXq4fkw8b6eDi6sxlKQhxHkU25viTDQBvzad7sIn_ENOvnRa3Mri8OdIvIn8F6Uw3GYtyv9qFpo8SCPvPE8WDy-8g6RN0ap7_05KN5D3CndM4klpS1Phb1tRzWq0nWQzHzz3k8P2ssew6909sb7Go3H9yACJ_RVevR7eN1thA9KaFUpqEZ9q2u3ep3qT51jImjTX9od0auKHLqKB__Yyz9G8NJxYmovX9RQ0s1--84LCWnRS-JAM4n6Ahv1Az1gpiNa3Yg1yMuG9oC50XF4lIpA6eSgNfL5fIbAzTuRucBAAA"
}
}
},
"signature": "0IB7oMwkB7cINtKJmKnoI0CKFxvt1YYnhT77vXPV-9yUn2dEG4_JrrXTW_QsryPPS6y7vzGQC4XWwjUhXCCLSsDe"
}response (new token)
{
"payload": {
"access": {
"nonce": "0ADWlMMYKbaPZcPNd9C73Ny_",
"serverIdentity": "1AAIA68_K08yASZus-UFGqzXwORIMQqP9581WUypmElmLZ8d"
},
"response": {
"access": {
"token": "0IBJVNOWejO-HHN7lssJ93PgGYxCE0EyvKPCEfOb0ETr0fEBRvVvCCwzuyK20KfA6v3EhrtY6K6P6ixC1hr8EJUfH4sIAAAAAAACA2WP23KbMBRF_0XPpSOBZRresI0viCaQkATT6WREELYyBmFJ3JLxv1d-STPt65m19t7nAygmeyZ3JWs01xPwAPL9nd-osv3xJvWYQkjDXRWLvJo_VockZ47w02O-H_ls2CTJGoNvoGQ9f2XGDch8Q8m6S8I4LcnzexoUSz9ctamD6P1ThgtB4sPt3t1nS2I8_rc1IPphPzs_9MvFgiQhjaMIc4k2bBh47Tyt6kO0nSjPulVxNGbbFSf-Stjn4HHw5Zk4C-GM_KRv3_Bw7hX-SdzAZTg_i-AFV5ZY11aeGV0KTTUXzZaq47X8roPZOOKM3ok-CmLFLcqLGCnUN49La9sEYVTgw-plS6frbKU6VvramDa0sYWghW5S5Hr23IPud3jj5oZiY8vl9B8zQ18YySrJ1DH4F7VhCvFnnH1FqdaSF51mCngfoGWyNjPMC2ox3YsTux5pWfMGeL9MLC2NMkiuGfh9uVz-AM3P8lbnAQAA"
}
}
},
"signature": "0IBlXrRpgo3iURV0EIXLEfi9GCUaOmtmnsUafJvT-4HTrjqPzY00DFpdbnGK1-wJowunfGnrsFo4h8Exj5CqIxxT"
}This protocol updates the recovery key hash to a new hash for a given identity.
request
{
"payload": {
"access": {
"nonce": "0ACUki5ud0-U3oYJW0IeoJOQ"
},
"request": {
"authentication": {
"device": "EIE_OcS_NTmW_qviA11FJRzXUmlw-H04GNkVunkvSFUb",
"identity": "EJHrDLVaac6PHnE-VtdpieFRzOGQD1qDK6m93xmGMwDd",
"publicKey": "1AAIA02sReVcy_PH9u6SbowgQxtTgU_U4wc638hry-xvTD3a",
"recoveryHash": "EJHPQs7ddvTm-p0cI62zcwg9d9jdgY38GzUgswUMIr1v",
"rotationHash": "ENCKdkGXWiaQb16VRl1Efj9_tAMs-fs1c7l0MCEKdl3h"
}
}
},
"signature": "0IA7Gjk3zOUcfwOV3Wl_MaQJB6SiGAG1w1c0BWzlKdAoOPYtWu2IPakxNtjm44nS_8Nn4Z6m5oQiu32tumiFXM9r"
}response
{
"payload": {
"access": {
"nonce": "0ACUki5ud0-U3oYJW0IeoJOQ",
"serverIdentity": "1AAIAuaNDFO9drP0R50q02vMQQAVl5ePSjIDeUqAhxRrBSdx"
},
"response": {}
},
"signature": "0IDiUm3xC407y3BlkzeB7hBS0bC5HBkS-5cgLtF8ehWJ-PdhTitLUdOkqYkZp6CVglj5_Yy1wNlDz2whi0PKOSJF"
}An access request is a resource request that is verified using the embedded token.
request
{
"payload": {
"access": {
"nonce": "0ADbScJs8Q_ygA0DZGlkOL1t",
"timestamp": "2025-10-10T07:00:29.423000000Z",
"token": "0IBnfopW9UnJRTsScouJPYtrj4_UKWtZZ4QP4DP--7-F569u3TWf8OFrQSXNCCBXZdwZ6gDv1qlJtIg67AIofer3H4sIAAAAAAACA22PW2_iMBCF_4uftyvbhFveAmRFNoRbSptSrVAuAzG5ONjOBSr--7qVdvvQjuZpdL5zzrwhCaIB4SRQKqauyETEshyLxU7jVPGzk3kvcDosx61TtgEcdvmWVjwKDrseO8CeF7cl-oESaFgMmrVXpZcefw2mjjttArHPtl279ibb68BrT60_8fB8kEaknsGt1hz7TLVn9aysY9pRsjh2-XrTNK6_4eHqspp6FWdGFNXBcJZLxz5psqqjnMUu_C9828luPrUu2ehpcd50VuYWRff4zP1eTKXy_SeyxOJi2YmRaVxwFSrGy3ko04_wbDNknA6JfTTw7WHFR0Zatc369zng2f7cD9NRdXJfFkUA77WlrCGxlCYppv0HgvU-4qGJsUnHPw1K8cfstRa6ionrFyXpf1EKOAqQqf0NQMb_rEnvEwiVEiyqFUhkvqEKRKGL6afk5LrlObwfw6RgJTJftXmYaKQVTAH6c7_f_wKu4aOm-QEAAA"
},
"request": {
"foo": "bar",
"bar": "foo"
}
},
"signature": "0IAOA9rrhzyB9VcL3aXPJWbVD-j4ju6Zol3_xG_wsJf9QWRgL_wZbE7kbokLmesHUmOPbLbhzlSbvZbwUXefF5DE"
}response
{
"payload": {
"access": {
"nonce": "0ADbScJs8Q_ygA0DZGlkOL1t",
"serverIdentity": "1AAIA3gwJej58j_uVqUln-CjkaRihnQophMChhFNq_6bBvRE"
},
"response": {
"wasFoo": "bar",
"wasBar": "foo"
}
},
"signature": "0IBDGQCj_tZyyXw_vY7a3AHFIASc3eCfHb_diU8iHnmjHbowIGjqeyohrV0L62c21W5gRAU9yTGDzLfxbpaky5CL"
}We can decode a token in a shell (the first 88 bytes is the signature, in this case):
TOKEN="0IBJVNOWejO-HHN7lssJ93PgGYxCE0EyvKPCEfOb0ETr0fEBRvVvCCwzuyK20KfA6v3EhrtY6K6P6ixC1hr8EJUfH4sIAAAAAAACA2WP23KbMBRF_0XPpSOBZRresI0viCaQkATT6WREELYyBmFJ3JLxv1d-STPt65m19t7nAygmeyZ3JWs01xPwAPL9nd-osv3xJvWYQkjDXRWLvJo_VockZ47w02O-H_ls2CTJGoNvoGQ9f2XGDch8Q8m6S8I4LcnzexoUSz9ctamD6P1ThgtB4sPt3t1nS2I8_rc1IPphPzs_9MvFgiQhjaMIc4k2bBh47Tyt6kO0nSjPulVxNGbbFSf-Stjn4HHw5Zk4C-GM_KRv3_Bw7hX-SdzAZTg_i-AFV5ZY11aeGV0KTTUXzZaq47X8roPZOOKM3ok-CmLFLcqLGCnUN49La9sEYVTgw-plS6frbKU6VvramDa0sYWghW5S5Hr23IPud3jj5oZiY8vl9B8zQ18YySrJ1DH4F7VhCvFnnH1FqdaSF51mCngfoGWyNjPMC2ox3YsTux5pWfMGeL9MLC2NMkiuGfh9uVz-AM3P8lbnAQAA"
echo -n ${TOKEN:88} | base64 -d | gunzip | jq .produces
{
"serverIdentity": "1AAIAnsdp8jrtxT00aJIfPoZf6UfgQZe3oAThZYxi4wGQQF5",
"device": "EK6GaKFuQJPTdKWzTEbCAJDpT31aRVX5boKPgNY7YXCK",
"identity": "EKtSY4qSvCBBKQJaPLL5ir1Gewwim3VDmgLHyaiXuDbh",
"publicKey": "1AAIAxwArqK3Bo3xiltNj5wqvs5MK7E7e5ZqoE_5f-oFm-ZX",
"rotationHash": "EOu0Xxx5XaOovLEPsi-aibP1s1vnUC-HnEJLb5gD_Hay",
"issuedAt": "2025-10-19T17:26:07.097Z",
"expiry": "2025-10-19T17:41:07.097Z",
"refreshExpiry": "2025-10-20T05:26:07.092Z",
"attributes": {
"permissionsByRole": {
"admin": [
"read",
"write"
]
}
}
}