Interactive Auth

An OTP auth layer over devframe's node-side primitives (exchangeTempAuthCode / verifyAuthToken / revokeAuthToken), so a host framework needn't re-implement the protocol.

An OTP auth layer over devframe's node-side primitives (exchangeTempAuthCode / verifyAuthToken / revokeAuthToken), so a host framework needn't re-implement the protocol.

Adapters gate with this layer by default via createDevServer(def) / initDevframe / initHub (auth: true). Use createInteractiveAuth only for a custom transport:

import { createContextRpcServer } from 'devframe/internal'
import { createInteractiveAuth } from 'devframe/recipes/interactive-auth'
import { attachWsRpcTransport } from 'devframe/rpc/transports/ws-server'

const auth = createInteractiveAuth(ctx, {
  clientAuthTokens: process.env.CI ? [process.env.DEVFRAME_CI_TOKEN!] : undefined,
})

const { rpcGroup, onConnected, onDisconnected } = createContextRpcServer({ context: ctx, auth })
attachWsRpcTransport(rpcGroup, { server, onConnected, onDisconnected })
auth.printBanner()

As auth it wires rpcFunctions, authorize, and onConnect; see Security.

createInteractiveAuth(context, options?)

OptionDefaultPurpose
clientAuthTokensundefinedPre-shared bearer tokens, always trusted.
bannera small boxed console messageCalled with { code, url, expireAt, requester? } (requester is the asking browser client's { ua, origin }, present on client-requested prints); prints via printBanner().
onTrustedundefinedCalled with { session, authToken } (the trust session and its token) once a code exchange succeeds, so a host framework rendering its own banner can retract it.
serverUrlcontext.host.resolveOrigin()Magic-link base URL.

Returns a DevframeAuthHandler:

FieldPurpose
rpcFunctionsanonymous:devframe:auth + anonymous:devframe:auth:exchange (handshake), anonymous:devframe:auth:request-code (client-requested banner print, reissue: true rotates the code first), devframe:auth:revoke (self-revoke).
authorize(methodName, session)Resolver gate: allows anonymous: methods, else requires session.meta.isTrusted.
onConnect(peer, session)Connect-time trust from a bearer on the WS upgrade URL (?devframe_auth_token=).
printBanner()Prints the code + magic-link URL, at most once per code.

Using the pieces directly

Without createContextRpcServer, wire the pieces directly:

const auth = createInteractiveAuth(ctx)
auth.rpcFunctions.forEach(fn => ctx.rpc.register(fn))
auth.printBanner()

// in your resolver:
if (!auth.authorize(methodName, session))
  throw new Error('not authorized')

// on each new WS peer:
auth.onConnect(peer, session)

The banner prints on demand: an untrusted browser client requests it over anonymous:devframe:auth:request-code (the RPC client's requestAuthCode(), sent when an auth UI first shows or its "re-issue" action runs), or the host calls auth.printBanner() itself. An exchange rotates the code silently, and onTrusted fires so a host framework rendering a sticky notice can retract it.

Auth storage is internal, not devframe/node/hub-internals.