ARCP supports two bearer-style auth mechanisms: static bearer tokens and signed JWTs. Both are enforced during the session handshake (RFC §8.2).
StaticBearerAuth maps a token string to a principal name. Comparison uses
constant-time equality to resist timing attacks.
val auth = StaticBearerAuth(mapOf(
"token-alice" to "alice",
"token-bob" to "bob",
))
val runtime = ARCPRuntime(
supportedCapabilities = Capabilities(),
bearerAuth = auth,
)The principal name (e.g. "alice") is stored in the session and used for
job-scoped lease and credential checks. An empty or whitespace token always
fails; ARCPException.Unauthenticated is thrown if the token is not in the
map.
JwtAuth validates a signed JWT against an expected audience. Use
JwtAuth.hmac() for HMAC-SHA256 shared-secret tokens:
val secret = System.getenv("ARCP_JWT_SECRET").toByteArray()
val jwtAuth = JwtAuth.hmac(secret, audience = "arcp-runtime")
val runtime = ARCPRuntime(
supportedCapabilities = Capabilities(),
jwtAuth = jwtAuth,
)JwtAuth validates:
- Signature — using the provided
JWSVerifier aud— must matchexpectedAudiencesub— must be non-blank (becomes the principal name)exp— token must not be expirednbf— token must be active (if present)
Any failure throws ARCPException.Unauthenticated.
For asymmetric keys (RSA, EC), supply a JWSVerifier directly:
val publicKey: RSAPublicKey = loadPublicKey()
val verifier = RSASSAVerifier(publicKey)
val jwtAuth = JwtAuth(verifier, expectedAudience = "arcp-runtime")ARCPClient takes an Auth block (the wire credentials block sent on
session.open); use ARCPClient.bearer(token) for the common case:
val client = ARCPClient(
transport = clientTransport,
auth = ARCPClient.bearer("token-alice"), // returns Auth(scheme = BEARER, token = ...)
client = ARCPClient.defaultClientInfo(),
capabilities = Capabilities(),
)The credentials ride in SessionOpen itself rather than waiting for a
challenge round-trip; the four-message handshake (RFC §8.1) only adds a
session.challenge/session.authenticate pair when the runtime needs
additional proof (e.g. a JWT nonce signature).
If the runtime requires authentication, the handshake is:
client ─── SessionOpen ──────────────────────> runtime
<── SessionChallenge ─────────────────
─── SessionAuthenticate (bearer/JWT) ──>
<── SessionAccepted ──────────────────
If the runtime accepts anonymous sessions (Capabilities(anonymous = true)),
it may skip the challenge and go straight to SessionAccepted.
The runtime assigns each session a TrustLevel based on authentication
(RFC §15.3):
| Level | Description |
|---|---|
UNTRUSTED |
No valid credentials |
CONSTRAINED |
Authenticated but restricted capabilities |
TRUSTED |
Fully authenticated principal |
PRIVILEGED |
Elevated administrative access |
The trust level is reported by the runtime on SessionAccepted.runtime:
val session = client.open()
val trust = session.runtime.trustLevel // dev.arcp.messages.TrustLevel