Vintraa Connect: sign in to your app from Vintraa
Vintraa Connect is an opt-in integration for external customer apps. It uses OpenID Connect Authorization Code, PKCE S256, state, nonce and private_key_jwt client authentication. Your existing external launch integration continues to work until you submit and activate a new release that declares this profile. Availability is configured separately in staging and production; a staging test does not publish your app to production.
What developers implement
- Provide an HTTPS POST start route (for example /app/launch) and an HTTPS GET callback (/app/connect/callback). Register exact URLs in your release manifest. Verify the incoming POST Origin against the Vintraa website for your platform environment. Require a top-level navigation and reject missing or null origins.
- Keep an Ed25519 private key on your server and put its public key in the release's existing customerExperience client authentication declaration. Register the Connect profile below. The callback must be on the reviewed launch origin for that execution environment. Callback changes require a new release and review.
- Use VintraaConnect from npm @zyntrialabs/vintraa-agent-sdk, or vintraa_agent.connect.VintraaConnect from Python vintraa-agent[connect]. Configure platform, execution environment, release ID, callback URL, key ID and private key on the server. After creating a candidate, the Test and submit page shows its release ID, issuer, exact client IDs and callbacks in “Vintraa Connect for this release”. The client ID is vca_<release UUID>_sandbox or vca_<release UUID>_production. Add each new release to your server allowlist before testing; retain old IDs while their releases remain supported. Never derive the trusted issuer or permitted client IDs from the incoming browser payload.
- Implement ConnectTransactionStore using durable encrypted server storage. Its take operation must atomically match both state and browser binding and remove the transaction. A wrong binding must not consume the legitimate transaction. Begin returns an authorization URL and a Secure, HttpOnly, SameSite=Lax transaction cookie. Save the cookie before redirecting. Never put the PKCE verifier in browser storage.
- In the callback, construct the URL with your configured app origin, pass the transaction cookie to finish, then clear that cookie. The SDK checks the authorization response, state, nonce, issuer, audience, signature and PKCE exchange. Store the returned ConnectSession encrypted on your server. Give the browser only a new random opaque app-session cookie (Secure, HttpOnly, SameSite=Lax, host-only, Path=/).
- Call current on every protected request, including local app data reads and billing/setup routes. Use the returned installation, workspace, environment, mode and current allowed actions to enforce authorization. Never accept those values from form inputs, a URL or an unverified JWT. Fail closed during a Vintraa outage, preserve unsaved work and offer Retry.
- After callback success, redirect to a clean app URL. Show whether the app is in Sandbox or Production. Keep sandbox databases, credentials, payments and effects separate; a sandbox label alone does not isolate data.
SDK installation and server configuration
SDK 0.2.0 is published on npm and PyPI, and Vintraa Connect is enabled on the staging and production platforms. Apps must implement the server routes, register a new release and pass review before activating this profile. Use Node.js 20.11+ or Python 3.11+. Install on the server:
npm install @zyntrialabs/[email protected]
python -m pip install 'vintraa-agent[connect]==0.2.0'TypeScript configuration (transactionStore is your encrypted durable adapter):
import { VintraaConnect } from "@zyntrialabs/vintraa-agent-sdk";
const connect = new VintraaConnect({
platform: "staging",
environment: "sandbox",
releaseId: process.env.VINTRAA_RELEASE_ID!,
callbackUrl: "https://sandbox.example.com/app/connect/callback",
keyId: process.env.VINTRAA_APP_KEY_ID!,
privateKey: process.env.VINTRAA_APP_PRIVATE_KEY!,
transactions: transactionStore,
});
// Origin-checked POST /app/launch:
const start = await connect.begin({ launchId, issuer, clientId });
// Send start.cookie as Set-Cookie, then 303 to start.authorizationUrl.
// GET callback, with the unique transaction-cookie value:
const session = await connect.finish(callbackUrlWithQuery, browserSecret);
// Save session encrypted under an opaque app-session cookie.
// Every protected request:
const current = await connect.current(session);
const { identity, installationId, workspaceId, mode, environment } = current.context;Python configuration (the sync API belongs in a sync route or worker thread):
import os
from vintraa_agent.connect import VintraaConnect
connect = VintraaConnect(
platform="staging",
environment="sandbox",
release_id=os.environ["VINTRAA_RELEASE_ID"],
callback_url="https://sandbox.example.com/app/connect/callback",
key_id=os.environ["VINTRAA_APP_KEY_ID"],
private_key=os.environ["VINTRAA_APP_PRIVATE_KEY"],
transactions=transaction_store,
)
# Origin-checked POST /app/launch:
start = connect.begin(launch_id=launch_id, issuer=issuer, client_id=client_id)
# Send start["cookie"] as Set-Cookie, then 303 to start["authorization_url"].
# GET callback, with the unique transaction-cookie value:
session = connect.finish(callback_url_with_query, browser_secret)
# Save session encrypted under an opaque app-session cookie.
# Every protected request:
current = connect.current(session)
identity = current["context"]["identity"]The start POST fields are vintraa_launch_id, vintraa_connect_issuer and vintraa_connect_client_id. Treat them as untrusted candidates and configure permitted clients in advance. Do not use vintraa_launch_code with the legacy exchange when the release declares Connect. In the callback use the server-configured callback URL plus the incoming query string, and read exactly one __Host-vintraa-connect-transaction cookie. Never render or log the query string.
For durable transaction storage, encrypt each transaction with authenticated encryption and a persistent server key, bind ciphertext to its record ID, and expire it after five minutes. Hash the state for the lookup key. Atomically delete and return the record only when the state hash, browser binding and expiry match. A read followed by a separate delete is unsafe across replicas. Keep app sessions encrypted separately; do not store credentials in localStorage or return them in JSON to the browser.
Release manifest extension
{
"x-vintraa.com-connect": {
"profile": "oidc-v1",
"callbackUrl": "https://app.example.com/app/connect/callback",
"sandboxCallbackUrl": "https://sandbox.example.com/app/connect/callback"
}
}Use manifest schemaVersion 2.0 for the extension. Keep your existing customerExperience.launchUrl, allowedOrigins and Ed25519 client authentication. Set the sandbox launch URL in x-vintraa.com-environments. If sandbox shares the same launch origin, sandboxCallbackUrl may be omitted and callbackUrl is used. Separate origins require separate callbacks. Redirect wildcards, query strings, fragments and a root-path callback are rejected. The platform verifies domain ownership before publication. A frozen release is immutable: create a new version under the same agent.
Platform environments
| Platform | OIDC issuer | Trusted start-route Origin |
|---|---|---|
| Staging | https://vintraa-be-staging.up.railway.app/connect/oidc | https://vintraa-fe-staging.up.railway.app |
| Production | https://api-vintraa.zyntrialabs.com/connect/oidc | https://vintraa.zyntrialabs.com |
Discover metadata at <issuer>/.well-known/openid-configuration. Connect signing keys are advertised by discovery; they are separate from runtime invocation signing keys. Both SDKs require explicit platform selection. The platform environment (staging/production) is separate from the execution environment (sandbox/production).
Identity and permissions
Use the tuple (issuer, subject) for the app user. Subject is an opaque app-specific ID, stable across that app's release versions and separated by execution environment. It is not an email address. Workspace IDs currently identify the Vintraa workspace and are not app-specific aliases. Workspace and installation are separate authorization scopes; one person may belong to multiple workspaces. Private review/testing mode must remain isolated even when the same user later hires the app.
The first profile shares no name or email and never forwards the user's Vintraa account JWT or password. Do not merge an existing app account by email or guess which account belongs to this user. If you support account linking, require proof of both accounts and explicit confirmation. An ID token proves a sign-in event; it is not permission to perform arbitrary actions or retain access indefinitely.
Confidential client assertions must contain recent integer iat and exp claims and expire within five minutes. The SDKs use 60-second assertions; do not extend them or reuse their JWT IDs.
Sessions expire after at most 15 minutes and cannot outlive the initiating Vintraa account session. There is no offline access or refresh token in this profile. On expiry, show a clear return-to-Vintraa action. Logout, account revocation, membership removal, app disconnection and installation/release suspension are enforced on the next live session check. This is live validation, not a promise of push logout to every browser. Users can disconnect sessions from /connect/connections.
Activity and billing
The successful callback acknowledges an app connection. Vintraa records the connection time; this means the app server completed sign-in, not that a screen rendered or a customer used a paid feature. The publisher receives the current app-specific identity and installation context. Do not treat launches, sign-ins, local analytics or browser events as billable usage. Submit usage through the reviewed runtime contract and obtain any required customer payment agreement separately.
Required tests before release
- Complete a private test, then repeat it in the same browser; test a second app and separate sandbox/production destinations.
- Reject a replayed callback, wrong state/nonce, wrong browser, wrong PKCE verifier, wrong issuer/client and altered callback URL.
- Confirm a user cannot read another workspace or installation's data, and a revoked role cannot perform its former actions.
- Sign out of Vintraa, remove membership, disconnect the app, suspend an installation and expire the session; protected app requests must stop.
- Refresh the app and return from test payment checkout; unsaved work and valid sessions should survive normally.
- Keep credentials out of URLs, browser storage, logs, screenshots and analytics. Authorization codes in the OIDC callback are single-use; redact callback query strings, send Referrer-Policy: no-referrer and Cache-Control: no-store, and redirect immediately to a clean URL.
- Do not load third-party scripts on launch/callback pages. Protect your own app mutations against CSRF. Rate-limit launch and callback routes and provide safe retry guidance.
Standards and reference implementation
The protocol follows OpenID Connect Core (https://openid.net/specs/openid-connect-core-1_0.html) and OAuth security guidance (https://www.rfc-editor.org/rfc/rfc9700.html). The SDKs use maintained OIDC libraries; do not replace their checks with a manual JWT decode. Care Rota is the reference app for the staging rollout. Successful sign-in is not a certification, billing approval or production activation.