Authentication API Reference
Complete HTTP API reference for all authentication and user management endpoints. All endpoints are prefixed with /api/user.
Endpoints Overview
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /register | No | Create a new account |
| POST | /login | No | Password-based login |
| POST | /request-direct-login | No | Get an ECIES challenge for passwordless login |
| POST | /direct-challenge | No | Submit a signed ECIES challenge to log in |
| GET | /verify | Yes | Validate a token and get user data |
| GET | /profile | Yes | Get full user profile with energy account |
| PUT | /profile | Yes | Update member settings (replication, storage) |
| POST | /change-password | Yes | Change password |
| POST | /recover | No | Recover account with mnemonic |
| POST | /logout | Yes | Invalidate server-side session |
| GET | /refresh-token | Yes | Get a fresh JWT |
| GET | /settings | Yes | Get user preferences |
| POST | /settings | Yes | Update user preferences |
| POST | /backup-codes | Yes | Generate backup codes |
| GET | /backup-codes | Yes | Get remaining backup code count |
| PUT | /backup-codes | Yes | Regenerate backup codes |
| POST | /recover-backup | Yes | Recover account with a backup code |
POST /register
Create a new user account. On success, returns a JWT token and the member’s recovery mnemonic.
Optionally accepts a user-provided BIP39 mnemonic phrase. When provided, the server validates its format (must be 12, 15, 18, 21, or 24 words), checks HMAC-based uniqueness, and derives the user’s cryptographic identity deterministically from it. When omitted, the server generates a new mnemonic automatically.
Request:
{
"username": "alice",
"email": "alice@example.com",
"password": "SecurePass123!",
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
}
The mnemonic field is optional. Omit it to have the server generate one for you.
201 Response:
{
"message": "Registration successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"memberId": "member-uuid",
"energyBalance": 1000
}
}
Validation is performed on all fields before processing. After creating the member, the server creates an EnergyAccount with trial credits and calls onPostRegister which creates a BrightHub social profile.
Errors:
400— Validation failure (missing fields, weak password, invalid email)400— Username or email already taken400— Invalid mnemonic format (not a valid BIP39 phrase of 12, 15, 18, 21, or 24 words)400— Mnemonic already in use (HMAC uniqueness collision)
POST /login
Authenticate with username and password.
Request:
{
"username": "alice",
"password": "SecurePass123!"
}
200 Response:
{
"message": "Logged in successfully",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"memberId": "member-uuid",
"energyBalance": 1000
}
}
The energyBalance field reflects the member’s current energy account balance.
Errors:
400— Missing or invalid fields401— Invalid credentials
POST /request-direct-login
Generate a server-signed challenge for ECIES-based passwordless login. This is the first step of the two-step direct challenge flow.
Request: Empty body (no parameters required).
200 Response:
{
"challenge": "hex-encoded-challenge-buffer",
"message": "Challenge generated",
"serverPublicKey": "hex-encoded-server-public-key"
}
Challenge buffer layout (hex-decoded):
| Offset | Length | Content |
|---|---|---|
| 0 | 8 bytes | Timestamp (big-endian uint64, milliseconds) |
| 8 | 32 bytes | Random nonce |
| 40 | 64 bytes | Server ECIES signature over timestamp ‖ nonce |
The client must sign this challenge with their member private key and submit it to /direct-challenge before the challenge expires (default: 5 minutes).
POST /direct-challenge
Submit a signed ECIES challenge to authenticate without a password. Requires a challenge obtained from /request-direct-login.
Request:
{
"challenge": "hex-encoded-challenge-buffer",
"signature": "hex-encoded-member-signature",
"username": "alice"
}
Either username or email may be provided to identify the member.
200 Response:
{
"message": "Logged in successfully",
"user": {
"id": "member-uuid",
"username": "alice",
"email": "alice@example.com",
"roles": [
{
"_id": "role-member-uuid",
"name": "User",
"admin": false,
"member": true,
"child": false,
"system": false,
"createdAt": "2026-03-01T12:00:00.000Z",
"updatedAt": "2026-03-01T12:00:00.000Z",
"createdBy": "member-uuid",
"updatedBy": "member-uuid"
}
],
"rolePrivileges": { "admin": false, "member": true, "child": false, "system": false },
"emailVerified": true,
"timezone": "UTC",
"siteLanguage": "en-US",
"darkMode": false,
"currency": "USD",
"directChallenge": false
},
"token": "eyJhbGciOiJIUzI1NiIs...",
"serverPublicKey": "hex-encoded-server-public-key"
}
Verification steps performed server-side:
- Parse the challenge buffer
- Verify the timestamp has not expired
- Verify the server’s ECIES signature on
timestamp ‖ nonce - Look up the member by username or email
- Verify the member’s ECIES signature on the full challenge
- Check the nonce has not been used before (replay prevention)
- Store the nonce in
UsedDirectLoginTokencollection
Errors:
401— Challenge expired, invalid challenge, invalid credentials, or challenge already used500— Unexpected server error
GET /verify
Validate the current JWT and return the authenticated user’s data. Used by the frontend on app load to check if a stored token is still valid.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Token is valid",
"user": {
"id": "member-uuid",
"username": "alice",
"email": "alice@example.com",
"roles": [
{
"_id": "role-member-uuid",
"name": "User",
"admin": false,
"member": true,
"child": false,
"system": false,
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z",
"createdBy": "member-uuid",
"updatedBy": "member-uuid"
}
],
"rolePrivileges": { "admin": false, "member": true, "child": false, "system": false },
"emailVerified": true,
"timezone": "America/New_York",
"siteLanguage": "en-US",
"darkMode": false,
"currency": "USD",
"directChallenge": false,
"lastLogin": "2026-03-01T12:00:00.000Z"
}
}
Errors:
401— Missing, invalid, or expired token
GET /profile
Get the authenticated user’s full profile including energy account data and optional member store metadata.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Retrieved successfully",
"data": {
"memberId": "member-uuid",
"username": "alice",
"email": "alice@example.com",
"energyBalance": 1000,
"availableBalance": 950,
"earned": 200,
"spent": 50,
"reserved": 50,
"reputation": 100,
"createdAt": "2026-01-15T10:00:00.000Z",
"lastUpdated": "2026-03-10T08:30:00.000Z",
"profile": {
"status": "active",
"storageQuota": "10737418240",
"storageUsed": "1073741824",
"lastActive": "2026-03-10T08:30:00.000Z",
"dateCreated": "2026-01-15T10:00:00.000Z"
}
}
}
The profile field is only present if the member store has public profile data available.
Errors:
401— Not authenticated500— Server error
PUT /profile
Update the member’s replication and storage settings in the member store.
Headers: Authorization: Bearer {token}
Request:
{
"settings": {
"autoReplication": true,
"minRedundancy": 3,
"preferredRegions": ["us-east-1", "eu-west-1"]
}
}
200 Response: Same shape as GET /profile — returns the updated profile with energy account data.
Errors:
401— Not authenticated500— Server error
POST /change-password
Change the authenticated user’s password.
Headers: Authorization: Bearer {token}
Request:
{
"currentPassword": "OldPass123!",
"newPassword": "NewPass456!"
}
200 Response:
{
"message": "Password changed successfully",
"data": {
"memberId": "member-uuid",
"success": true
}
}
Errors:
400— Validation failure (missing fields, weak new password)401— Current password is incorrect500— Server error
POST /recover
Recover an account using the mnemonic phrase provided at registration.
Request:
{
"email": "alice@example.com",
"mnemonic": "word1 word2 word3 ... word12",
"newPassword": "NewPass789!"
}
200 Response:
{
"message": "Account recovered successfully",
"data": { ... }
}
Errors:
400— Validation failure401— Invalid credentials or invalid mnemonic500— Server error
POST /logout
Invalidate the server-side session associated with the current token.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Success"
}
The server validates the token via the BrightChainSessionAdapter, looks up the associated session, and deletes it. The client should also clear authToken and user from localStorage.
Errors:
401— Missing or invalid token500— Server error
GET /refresh-token
Exchange a valid JWT for a fresh one with a new expiration. Also returns the current user DTO and server public key.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Success",
"user": { ... },
"token": "eyJhbGciOiJIUzI1NiIs...",
"serverPublicKey": "hex-encoded-server-public-key"
}
The new token is also returned in the Authorization response header as Bearer {new-token}.
Errors:
401— Missing or invalid token500— Server error
GET /settings
Get the authenticated user’s display preferences.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Settings retrieved",
"settings": {
"email": "alice@example.com",
"timezone": "America/New_York",
"currency": "USD",
"siteLanguage": "en",
"darkMode": false,
"directChallenge": false
}
}
Errors:
401— Not authenticated
POST /settings
Update the authenticated user’s display preferences. Only provided fields are updated; omitted fields are left unchanged. Settings are persisted to the user_settings collection.
Headers: Authorization: Bearer {token}
Request:
{
"timezone": "Europe/London",
"darkMode": true,
"siteLanguage": "fr",
"currency": "EUR",
"directChallenge": true
}
All fields are optional.
200 Response:
{
"message": "Settings saved",
"user": { ... }
}
Returns the full updated user DTO.
Errors:
401— Not authenticated500— Server error
POST /backup-codes
Generate a new set of 10 backup recovery codes. Codes are encrypted with ECIES and hashed with Argon2id before storage.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Your new backup codes",
"backupCodes": ["code1", "code2", "...", "code10"]
}
These codes are displayed once. The user must store them securely.
Errors:
401— Not authenticated500— Generation failed
GET /backup-codes
Get the number of remaining unused backup codes.
Headers: Authorization: Bearer {token}
200 Response:
{
"message": "Backup codes retrieved",
"codeCount": 8
}
Errors:
401— Not authenticated500— Retrieval failed
PUT /backup-codes
Regenerate all backup codes, invalidating any previously generated codes.
Headers: Authorization: Bearer {token}
200 Response: Same shape as POST /backup-codes.
Errors:
401— Not authenticated500— Generation failed
POST /recover-backup
Recover account access using a backup code. Optionally set a new password.
Headers: Authorization: Bearer {token}
Request:
{
"backupCode": "abc123def456",
"newPassword": "OptionalNewPass!"
}
newPassword is optional.
200 Response:
{
"message": "Recovery successful",
"codeCount": 7
}
Returns the remaining backup code count after consuming the used code.
Errors:
400— Missing backup code401— Invalid backup code500— Server error
Role Object
Roles in user responses are full objects, not simple strings. Each role has the following shape:
{
"_id": "role-member-uuid",
"name": "User",
"admin": false,
"member": true,
"child": false,
"system": false,
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z",
"createdBy": "member-uuid",
"updatedBy": "member-uuid"
}
| Field | Type | Description |
|---|---|---|
_id | string | Role document ID (format: role-{memberId}) |
name | string | Role name, derived from MemberType (e.g., "User", "Admin", "System") |
admin | boolean | Whether this role grants global admin privileges |
member | boolean | Whether this role grants standard member privileges |
child | boolean | Whether this is a child/restricted role |
system | boolean | Whether this is a system-level role |
createdAt | string (ISO 8601) | When the role was created |
updatedAt | string (ISO 8601) | When the role was last updated |
createdBy | string | Member ID of the role creator |
updatedBy | string | Member ID of the last updater |
The rolePrivileges field on user responses is a convenience summary that combines all assigned roles into a single { admin, member, child, system } object using OR logic across all roles.
Cryptographic Specification
BrightChain authentication is built on ECIES (Elliptic Curve Integrated Encryption Scheme) with secp256k1. This section documents the cryptographic parameters that clients need for key derivation, signing, and encryption.
Identity Key Derivation
Member identities are derived from a BIP39 mnemonic phrase using BIP32 hierarchical deterministic key derivation:
| Parameter | Value |
|---|---|
| Mnemonic standard | BIP39 |
| Mnemonic entropy | 256 bits (24 words) |
| Elliptic curve | secp256k1 (same as Bitcoin/Ethereum) |
| Primary derivation path | m/44'/60'/0'/0/0 (BIP44, coin type 60 = Ethereum) |
| Security level | 128-bit (ECDSA on secp256k1) |
The mnemonic produces a seed via bip39.mnemonicToSeedSync(), which feeds into HDKey.fromMasterSeed() for BIP32 derivation. The derived key pair is the member’s identity: the public key is their network identity, and the private key is used for signing and decryption.
Key Derivation Paths
BrightChain uses the BIP44 change field to separate key purposes:
| Purpose | Derivation Path | Description |
|---|---|---|
| Primary identity / wallet | m/44'/60'/0'/0/0 | Member’s main key pair |
| Device provisioning | m/44'/60'/0'/1/<index> | Per-device keys (index increments per device) |
| Git signing | m/44'/60'/0'/2/0 | Commit and tag signing |
All purpose-level paths use hardened derivation (') to prevent child key compromise from leaking parent keys.
ECDSA Signing
| Parameter | Value |
|---|---|
| Algorithm | ECDSA |
| Curve | secp256k1 |
| Signature size | 64 bytes (r: 32 bytes, s: 32 bytes) |
| Public key (uncompressed) | 65 bytes (0x04 prefix + 64 bytes) |
| Public key (compressed) | 33 bytes (0x02/0x03 prefix + 32 bytes) |
| Public key magic byte | 0x04 (uncompressed format) |
Signatures are used for:
- Direct challenge authentication (member signs the server’s challenge buffer)
- Block and CBL creator signatures
- Identity proofs (self-verifying, no server trust required)
Symmetric Encryption (ECIES Payload)
| Parameter | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key size | 256 bits (32 bytes) |
| IV size | 16 bytes |
| Authentication tag size | 16 bytes |
ECIES combines ECDH key agreement (on secp256k1) with AES-256-GCM for authenticated encryption. The ephemeral public key, IV, auth tag, and ciphertext are packed into the encrypted message.
Password-Based Key Wrapping
When a user registers with a password, the member’s private key is wrapped (encrypted) using a key derived from the password:
| Parameter | Value |
|---|---|
| KDF | PBKDF2 |
| Salt size | 32 bytes |
| IV size | 16 bytes |
| Master key size | 32 bytes (256 bits) |
| Minimum iterations | 100,000 |
| Wrapping algorithm | AES-256-GCM |
Mnemonic HMAC (Uniqueness Check)
When a user provides their own mnemonic during registration, the server computes an HMAC to check uniqueness without storing the plaintext:
| Parameter | Value |
|---|---|
| Algorithm | HMAC-SHA256 |
| Secret | MNEMONIC_HMAC_SECRET environment variable (hex-encoded) |
| Input | UTF-8 encoded mnemonic string |
| Output | 64-character hex string |
Only the HMAC is stored. The plaintext mnemonic is never persisted.
Mnemonic Format (BIP39)
Valid mnemonics must match the MnemonicRegex pattern from @digitaldefiance/ecies-lib:
| Word count | Entropy bits | Checksum bits |
|---|---|---|
| 12 words | 128 | 4 |
| 15 words | 160 | 5 |
| 18 words | 192 | 6 |
| 21 words | 224 | 7 |
| 24 words | 256 | 8 |
BrightChain generates 24-word mnemonics (256-bit entropy) by default.
Multi-Recipient Encryption
For encrypting data to multiple recipients (e.g., shared vaults, group messages):
| Parameter | Value |
|---|---|
| Max recipients | 255 |
| Encrypted key size per recipient | 48 bytes (IV + auth tag + symmetric key) |
| Recipient ID size | 12 bytes |
Each recipient’s copy of the symmetric key is encrypted with their ECIES public key.
Test Vectors
These test vectors are sourced from the cross-platform test suites in brightchain-cpp, brightchain-apple, and brightpass-apple. They can be used to verify that any client implementation produces identical results.
Key Derivation (BIP39 → secp256k1)
Vector 1 — Standard BIP39 test mnemonic (12 words):
| Field | Value |
|---|---|
| Mnemonic | abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about |
| Derivation path | m/44'/60'/0'/0/0 |
| Private key | 1ab42cc412b618bdea3a599e3c9bae199ebf030895b039e9db1e30dafb12b727 |
| Public key (compressed) | 0237b0bb7a8288d38ed49a524b5dc98cff3eb5ca824c9f9dc0dfdb3d9cd600f299 |
Vector 2 — Second BIP39 test mnemonic (12 words):
| Field | Value |
|---|---|
| Mnemonic | legal winner thank year wave sausage worth useful legal winner thank yellow |
| Derivation path | m/44'/60'/0'/0/0 |
| Private key | 33fa40f84e854b941c2b0436dd4a256e1df1cb41b9c1c0ccc8446408c19b8bf9 |
| Public key (compressed) | 03a70d1ef368ad99e90d509496e9888ee7404e4f4d360376bf521d769cf0c4de46 |
ECDSA Signing
Using Vector 1’s key pair:
| Field | Value |
|---|---|
| Data (hex) | deadbeef |
| Signature (hex, 64 bytes) | 1b2becafb75effaa2d4e4ad33876310a7b7190f9b0fe4c48de00abb7fac27ec1087d1c0c45dd1567233f5eb3fae458347cd4bd5eab7c506ceb3858daf69c336d |
Verify by recovering the public key from the signature over deadbeef and comparing to Vector 1’s public key.
ECIES Encryption
Cross-member encryption test: Vector 1 encrypts to Vector 2’s public key.
| Field | Value |
|---|---|
| Sender | Vector 1 (private key 1ab42c...) |
| Recipient | Vector 2 (public key 03a70d...) |
| Plaintext | Hello from TypeScript! (UTF-8) |
ECIES encryption is non-deterministic (uses an ephemeral key pair), so ciphertext will differ on each run. To verify:
- Encrypt the plaintext using Vector 2’s public key
- Decrypt using Vector 2’s private key
- Confirm the decrypted output matches the original plaintext
The ECIES implementation supports two modes:
- basic — standard ECIES (ephemeral key + ECDH + AES-256-GCM)
- withLength — prepends a 4-byte big-endian length prefix to the plaintext before encryption, enabling streaming/chunked decryption
SHA3 Hash Vectors
BrightChain uses SHA3-512 (Keccak) for content-addressable block hashing. Vectors sourced from brightchain-cpp/tests/test_vectors_sha3.json.
| Input | SHA3-512 Hash (hex, 128 chars) |
|---|---|
"" (empty) | a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26 |
"Hello, World!" | 38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed |
"1234567890" | 36dde7d288a2166a651d51ec6ded9e70e72cf6b366293d6f513c75393c57d6f33b949879b9d5e7f7c21cd8c02ede75e74fc54ea15bd043b4df008533fc68ae69 |
JSON object {"id":"test-123",...} | 3f13be01f1841deb0fe1240085313035408253105aa88847d8cab4554570d61477cd8340bb63399d25636a4024f332030d3240004be60bb3b7e7f54ca7569ae5 |
Block-size vectors (for verifying block hashing at various sizes):
| Input | SHA3-512 Hash (hex) |
|---|---|
512 bytes (0xaa repeated) | a238645e984279b4fad65f1490a5fa8d7143e60c0e4b14dfdcd3ad982da028acb28caef01f04dfcb58cd6b18986cbe4df29d53edd013be10d57b223c0a8a0c5b |
1 KB (0xbb repeated) | 2221bd465137f2c3ff2db524a0f391585ca3d4f82a6a61860f848da8007bf1ea434057ce1e4568081734df7a9be81173f2cdce04ea9e6c98510a7707c009a0ef |
1 MB (0xcc repeated) | 345f55104ed3566d0d5d6fe60d859d09819b528231e20ac0df33d3b08512873a51501c069dd8e904c19be41c250adad69bc716402c327e1aed0a00d1fb46ca4b |
Related Documentation
For deeper coverage of specific cryptographic subsystems:
- Keybase-Inspired Features Architecture — device provisioning, identity proofs, Ethereum wallet derivation, git signing
- Security Analysis — threat model and adversarial review
- BrightPass Password Manager — vault encryption model, Shamir’s Secret Sharing for emergency access
JWT Token Structure
Algorithm: HS256 (HMAC-SHA256) Expiration: 7 days from issuance
Payload:
{
"memberId": "member-uuid",
"username": "alice",
"type": "member",
"iat": 1741564800,
"exp": 1742169600
}
The type field corresponds to MemberType from @digitaldefiance/ecies-lib.
Authentication Middleware
All endpoints marked “Auth: Yes” are protected by createJwtAuthMiddleware. The middleware:
- Extracts the token from the
Authorizationheader (supportsBearer {token}and raw token formats) - Verifies the signature and expiration using
jsonwebtoken - Attaches an
IMemberContexttoreq.memberContextcontainingmemberId,username,type,iat,exp - Also sets
req.userfor backward compatibility
For optional authentication (e.g., public endpoints that behave differently for logged-in users), pass optional: true to allow requests without tokens to proceed.
Role-Based Access Control
Apply after the JWT middleware:
createRoleMiddleware({
requiredRoles: ['admin'],
requiredTypes: [MemberType.ADMIN],
requireAll: false // OR logic (any match grants access)
})
Convenience helpers: requireAuth(), optionalAuth(), requireRoles(), requireAllRoles(), requireMemberTypes(), requireAuthWithRoles(), requireAuthWithMemberTypes().
Standard Error Response
{
"message": "Human-readable error description",
"error": "Technical error detail"
}
Some validation errors include an errors array with per-field details. Errors from HandleableError include a statusCode that maps to the HTTP response code.