Blockstream Enterprise
Onboarding

Onboarding a Regular User

How a regular (human) user registers their device using an admin-issued invitation and establishes a persistent authenticated session.

Overview

Regular users are onboarded via an invitation flow. An admin creates the user account and issues an invitation containing one-time credentials. The user then uses those credentials to register their own session keys and receive a permanent device identity.

This is distinct from API user onboarding, where keys are submitted upfront during account creation. Here, the server issues temporary credentials first, and the user exchanges them for their own long-lived session keys.

How the invitation flow works

Admin                         Server                        User
  │                              │                            │
  │── POST /users (invite) ──────▶                            │
  │◀─ invite_string              │                            │
  │   oneTimePrivateKey          │                            │
  │   deviceUuid                 │                            │
  │   serverPublicKeys           │                            │
  │                              │                            │
  │── share invitation ──────────────-───────────────────────▶│
  │                              │                            │
  │                              │  generate session key pairs│
  │                              │                            │
  │                              │◀── POST /account-management/authorize
  │                              │    (invite_string, new pubkeys, password)
  │                              │                            │
  │                              │── session credentials ───▶│
  │                              │   user_id, device_uuid     │

What you receive in an invitation

FieldTypeDescription
invite_stringstringOne-time token issued by the server
oneTimePrivateKeyhex stringTemporary ECDSA private key for the authorization request
deviceUuidstringTemporary device UUID tied to the invitation
server_rsa_pub_key_pem_b64base64 stringServer RSA public key (base64-encoded PEM)
server_ecdsa_pub_keyhex stringServer ECDSA public key

Step-by-step

Step 1 — Generate your session key pairs

Before authorizing, generate two key pairs that will identify your device going forward. These are your permanent session credentials and never leave your environment.

import { p256 } from '@noble/curves/nist'
import forge from 'node-forge'
import { Buffer } from 'buffer'

// ECDSA P-256 — used for signing future requests
const sessionPrivateKey = p256.utils.randomSecretKey()
const sessionPublicKeyHex = Buffer.from(p256.getPublicKey(sessionPrivateKey, true)).toString('hex')

// RSA 2048 — used for encrypting/decrypting payloads
const rsaKeyPair = forge.pki.rsa.generateKeyPair({ bits: 2048, e: 0x10001 })

Step 2 — Decode the server public key

The server RSA public key in the invitation is base64-encoded. Decode it before passing it to the SDK:

const serverRsaPubKeyPem = forge.pki.publicKeyToPem(
  forge.pki.publicKeyFromPem(
    Buffer.from(invitation.server_rsa_pub_key_pem_b64, 'base64').toString('utf-8'),
  ),
)

Step 3 — Create a one-time SDK instance

Use the invitation's one-time private key and device UUID to build a temporary Blockstream instance. This instance is only used for the authorization request — it is not your session client.

import { Blockstream } from '@blockstream/ecs-js-sdk'

const oneTimeClient = new Blockstream(
  forge.pki.privateKeyToPem(rsaKeyPair.privateKey), // freshly generated RSA private key
  new Uint8Array(Buffer.from(invitation.oneTimePrivateKey, 'hex')), // one-time ECDSA key
  invitation.deviceUuid,
  serverRsaPubKeyPem,
  Buffer.from(invitation.server_ecdsa_pub_key, 'hex'),
)

Step 4 — Authorize and register your device

Send the authorization request with your new session public keys. The server will bind them to your user account and return your permanent device credentials.

import { v4 as uuidv4 } from 'uuid'

const response = await executeBroadcast(
  {
    action: 'add',
    resource: '/account-management/authorize',
    details: {
      invite_string: invitation.invite_string,
      device_name: `${uuidv4()}-device`,
      device_signature_pubkey: sessionPublicKeyHex,
      device_encryption_pubkey: forge.pki.publicKeyToPem(rsaKeyPair.publicKey),
      password: password,
    },
  },
  oneTimeClient,
)

Note on password: If a password is set here, all future devices registered to this account will require it. Leave it empty if your account does not use password protection.

Step 5 — Store your session credentials

On success, the server returns your permanent user_id and device_uuid. Store these alongside your session private keys — they are required to initialize the SDK on every subsequent request.

if (response.status !== 'success') {
  throw new Error(response.message ?? 'Authorization failed')
}

const session = {
  userId: response.details.user_id,
  deviceUuid: response.details.device.device_uuid,
  sessionPrivateKeys: {
    ecdsa: Buffer.from(sessionPrivateKey).toString('hex'),
    rsa: forge.pki.privateKeyToPem(rsaKeyPair.privateKey),
  },
  serverPublicKeys: {
    rsa: serverRsaPubKeyPem,
    ecdsa: invitation.server_ecdsa_pub_key,
  },
}

You can now initialize a permanent Blockstream instance with these credentials for all future requests.

Proposal state

If the server returns status: "pending" instead of "success", the authorization was submitted as a proposal and requires approval from another authorized user before the device is registered. Handle this case in your application flow:

if (response.status === 'pending') {
  // Inform the user that approval is required
  console.log('Awaiting approval for proposal:', response.details.proposal_id)
}

Security notes

  • The one-time private key from the invitation is single-use — discard it after authorization.
  • Store session private keys in a secure location (encrypted storage, secrets manager). They cannot be recovered from the server.
  • Each device has a unique key pair. To add another device, request a new invitation from an admin.

On this page