Skip to content

A flexible set of multi-device authentication protocols based on signatures and forward commitments.

License

Notifications You must be signed in to change notification settings

jasoncolburne/better-auth

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

77 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

better-auth

Better Auth is a pluggable authentication protocol, agnostic to platform, encoding, cryptographic and storage choices. Designed to be accessible, flexible and secure.

Examples

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.

Demo Deployment

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:

  1. Self-Addressing Identification (deriving ids from data they represent and embedding those identifiers in that data)
  2. Self-Certification (each document is signed by a key embedded in it)
  3. Forward Commitment (each document makes a commitment to the next key without revealing it, and that key is used to sign the next document)
  4. 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.

Rationale

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.

Getting Started

To clone this repository with all implementation submodules:

git clone --recurse-submodules [email protected]:jasoncolburne/better-auth.git
cd better-auth

If you've already cloned without --recurse-submodules, initialize the submodules:

git submodule update --init --recursive

To pull the latest changes from all submodules:

./scripts/pull-repos.sh

To set up all implementation repositories (install dependencies):

./scripts/run-setup.sh

This 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.

For Contributors

Git Hooks

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.sh

If you need to override the hook (not recommended):

git commit --no-verify -m "message"

Working with Submodules

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, commit

2. When ready, merge to main in the submodule:

git checkout main
git merge feature/my-feature
git push origin main

3. 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.

Running Tests

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 sequence

Each implementation has standardized Makefile targets:

  • make setup - Install dependencies
  • make test - Run unit tests
  • make type-check - Run type checker
  • make lint - Run linter
  • make format - Auto-format code
  • make format-check - Check formatting
  • make clean - Clean build artifacts

Full Example

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.

Design Principles

  1. All actions except authentication to acquire an access session are gated by a rotation operation.
  2. All requests contain a nonce to challenge the server.
  3. Authentication is a two phase operation that involves the server challenging the client.
  4. 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:

Protocols

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.
  • link This 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.

Account

CreateAccount

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"
}

DeleteAccount

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"
}

RecoverAccount

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"
}

Device

LinkDevice

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"
}

UnlinkDevice

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"
}

RotateDevice

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"
}

Session

RequestSession

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"
}

CreateSession

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"
}

RefreshSession

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"
}

Recovery

ChangeRecoveryKey

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"
}

Access

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"
}

Appendix

AccessToken encoding

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"
      ]
    }
  }
}

About

A flexible set of multi-device authentication protocols based on signatures and forward commitments.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages