Authentication

Authenticate with the Novacore API using challenge/verify signing with Ed25519 or ES256 keys.

Novacore uses a challenge/verify authentication flow. You request a challenge, sign it with your private key, and exchange the signature for a JWT token.

Identity Types

An identity in NovaCore represents who something is — a person, a gateway, a device, or a service. Identities are algorithm-agnostic: the authentication algorithm (Ed25519, ES256, etc.) is determined by the identity's auth methods, not its type. An identity can have multiple auth methods simultaneously.

Identities receive opaque IDs (idt-{uuid}) so the identity type cannot be inferred from the ID alone.

Identity TypeDescription
userHuman user, access scoped to organization membership and role, typically authenticated via Privy
gatewayZAP gateway device, proxies telemetry for connected hardware
deviceSmart device with direct MQTT connectivity (e.g. wallbox)
integrationThird-party service integration
developerDev/test service

Authentication Flow

1. Request a Challenge

POST https://novacore-mainnet.sourceful.dev/auth/challenge
Content-Type: application/json

{
  "public_key": "your-public-key"
}

Response:

{
  "challenge": "random-challenge-string",
  "expires_at": "2026-03-06T13:00:00Z"
}

2. Sign the Challenge

Sign the challenge string with your private key using the appropriate algorithm (Ed25519 for humans, ES256 for machines).

3. Verify and Get Token

POST https://novacore-mainnet.sourceful.dev/auth/verify
Content-Type: application/json

{
  "public_key": "your-public-key",
  "signature": "base64-encoded-signature",
  "challenge": "the-challenge-string"
}

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "identity_id": "usr-abc123"
}

4. Use the Token

Include the JWT in all subsequent API requests:

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

JWT Structure

Tokens are signed with HS256 and contain:

{
  "public_key": "<auth method public key>",
  "identity_type": "user",
  "identity_id": "usr-abc123",
  "auth_method_id": "<uuid>",
  "auth_method_type": "ed25519",
  "iss": "srcful-api",
  "exp": 1709740800
}

Tokens expire after 24 hours. When a token expires, repeat the challenge/verify flow to obtain a new one.

Code Examples

Python (Ed25519)

import requests
from nacl.signing import SigningKey
import base64

# Generate or load your Ed25519 key pair
signing_key = SigningKey.generate()
public_key = base64.b64encode(
    signing_key.verify_key.encode()
).decode()

BASE_URL = "https://novacore-mainnet.sourceful.dev"

# Step 1: Request challenge
resp = requests.post(f"{BASE_URL}/auth/challenge", json={
    "public_key": public_key
})
challenge = resp.json()["challenge"]

# Step 2: Sign the challenge
signature = base64.b64encode(
    signing_key.sign(challenge.encode()).signature
).decode()

# Step 3: Verify and get token
resp = requests.post(f"{BASE_URL}/auth/verify", json={
    "public_key": public_key,
    "signature": signature,
    "challenge": challenge,
})
token = resp.json()["token"]

# Step 4: Use the token
headers = {"Authorization": f"Bearer {token}"}
resp = requests.get(f"{BASE_URL}/organizations", headers=headers)
print(resp.json())

JavaScript/TypeScript (Ed25519)

import * as ed from "@noble/ed25519";

const BASE_URL = "https://novacore-mainnet.sourceful.dev";

// Generate or load your Ed25519 key pair
const privateKey = ed.utils.randomPrivateKey();
const publicKey = Buffer.from(
  await ed.getPublicKeyAsync(privateKey)
).toString("base64");

// Step 1: Request challenge
const challengeResp = await fetch(`${BASE_URL}/auth/challenge`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ public_key: publicKey }),
});
const { challenge } = await challengeResp.json();

// Step 2: Sign the challenge
const signature = Buffer.from(
  await ed.signAsync(
    new TextEncoder().encode(challenge),
    privateKey
  )
).toString("base64");

// Step 3: Verify and get token
const verifyResp = await fetch(`${BASE_URL}/auth/verify`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ public_key: publicKey, signature, challenge }),
});
const { token } = await verifyResp.json();

// Step 4: Use the token
const orgs = await fetch(`${BASE_URL}/organizations`, {
  headers: { Authorization: `Bearer ${token}` },
});
console.log(await orgs.json());

Knowledge gap for Johan: The exact request/response schemas for /auth/challenge and /auth/verify need verification. The examples above are based on the Novacore auth documentation but field names and formats may differ slightly in practice. Please verify and update.

Identity Registration

New identities may need to be registered before they can authenticate:

POST https://novacore-mainnet.sourceful.dev/identity/register
Content-Type: application/json

{
  "public_key": "your-public-key"
}

Knowledge gap for Johan: Is identity registration required before first auth, or does /auth/verify auto-register new public keys? What fields does the registration endpoint require?

Security Considerations

  • 24-hour token expiry: Tokens have a limited lifetime to reduce attack surface
  • Private key separation: Private keys never leave your application
  • No token refresh: When expired, re-authenticate with a new challenge/verify cycle
  • Transport security: Always use HTTPS