Authentication Flow
Portal uses Nostr key pairs: users prove identity by signing challenges. Your app gets an auth URL; the user opens it in a Nostr wallet and approves; you receive the key handshake and verify.
API
- Generate auth URL:
newKeyHandshakeUrl(onKeyHandshake, staticToken?, noRequest?)— callback runs when the user completes the handshake. - Authenticate the key:
authenticateKey(mainKey, subkeys?)— returns AuthResponseData with status (approved / declined), optional session_token, reason.
JavaScript
const authUrl = await client.newKeyHandshakeUrl(async (mainKey, preferredRelays) => {
const authResponse = await client.authenticateKey(mainKey);
if (authResponse.status.status === 'approved') {
// session_token in authResponse.status.session_token
}
});
// Share authUrl (QR, link, etc.) with user
Java
import cc.getportal.command.request.KeyHandshakeUrlRequest;
import cc.getportal.command.request.AuthenticateKeyRequest;
import cc.getportal.command.response.KeyHandshakeUrlResponse;
import cc.getportal.command.response.AuthenticateKeyResponse;
import java.util.List;
// 1) Get handshake URL (notification gives mainKey when user completes)
sdk.sendCommand(
new KeyHandshakeUrlRequest((n) ->
System.out.println("mainKey: " + n.main_key())),
(res, err) -> {
if (err != null) return;
System.out.println("URL: " + res.url());
}
);
// 2) Authenticate with key (after user completed handshake)
sdk.sendCommand(
new AuthenticateKeyRequest("user-pubkey-hex", List.of()),
(res, err) -> {
if (err != null) { System.err.println(err); return; }
System.out.println("authenticated");
}
);
HTTP
# 1. Get a key handshake URL
curl -s -X POST $BASE_URL/key-handshake \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# → { "stream_id": "abc123", "url": "nostr+walletconnect://..." }
# Show the URL to the user (QR code, link, etc.)
# Poll the stream to receive the user's public key when they complete the handshake.
# 2. Authenticate the key
curl -s -X POST $BASE_URL/authenticate-key \
-H "Authorization: Bearer $AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"main_key": "USER_PUBKEY_HEX", "subkeys": []}'
# → { "stream_id": "def456" }
# 3. Poll for result
curl -s "$BASE_URL/events/def456?after=0" \
-H "Authorization: Bearer $AUTH_TOKEN"
# → { "events": [{ "index": 0, "type": "StatusUpdate", "data": { "status": "approved", "session_token": "..." } }] }
See REST API for the full polling flow.
- Subkeys: Pass optional subkeys to
authenticateKey(JS) orAuthenticateKeyRequest(Java) for delegated auth. - Static token: Pass a string as second arg to
newKeyHandshakeUrl(JS) orKeyHandshakeUrlRequest(staticToken, noRequest, callback)(Java) for long-lived reusable URLs. - No-request mode: Third arg true (JS) or noRequest = true (Java) — handshake only, no auth challenge.
Check status === 'approved' before granting access. Use session tokens and expiration in your app; the SDK verifies signatures.
Next: Single Payments · Profiles · JWT Tokens