Go · backend sign-in
The official server-side relying-party client. Keep code exchange, tokens and session custody on your backend.
Install
Section titled “Install”GOPRIVATE=gitlab.com/jarvis-ecosystem/* \ go get gitlab.com/jarvis-ecosystem/jarvisapis/sdk/go@v0.2.0-alpha.1GitLab repository access is required. Never put a token in the module URL. The examples below are imported from source files that are compiled against this exact SDK version. They are integration functions, not a complete BFF.
Configure once on the backend
Section titled “Configure once on the backend”package integration
import ( "context"
"gitlab.com/jarvis-ecosystem/jarvisapis/sdk/go/auth")
// Supply an encrypted, expiring, shared FlowStore from your application.func NewIdentityClient(ctx context.Context, store auth.FlowStore, clientID, clientSecret, callbackURL string,) (*auth.Client, error) { return auth.New(ctx, auth.Config{ Issuer: "https://jarvis-id-dev.k8s1.aviproduction.org", Audience: "jarvis-personal-api", ClientID: clientID, ClientSecret: clientSecret, RedirectURI: callbackURL, // Exact URL registered for YOUR application. Store: store, })}auth.New fetches discovery and checks provider capabilities. Treat configuration
and discovery errors as startup failures, not a reason to disable validation.
Load the client secret from your server secret manager, never a VITE_* variable.
Supply a real FlowStore
Section titled “Supply a real FlowStore”The application implements Save(ctx, browserBinding, transaction) and
Consume(ctx, browserBinding, state). The store must:
- Encrypt the transaction, including its PKCE verifier and nonce, at rest.
- Expire it at
ExpiresAtand share it across backend replicas. - Match both binding and state and consume it atomically exactly once.
- Reject mismatches, expiry and replay with
auth.ErrFlow.
Do not substitute a global map or account ID for browser binding. The reference BFF contains a PostgreSQL-backed encrypted store.
Start and finish sign-in
Section titled “Start and finish sign-in”package integration
import ( "net/http"
"gitlab.com/jarvis-ecosystem/jarvisapis/sdk/go/auth")
// Wire this to your product's GET /auth/jarvis/start route.func StartSignIn(client *auth.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { binding := auth.NewBrowserBinding() target, err := client.Start(r.Context(), binding) if err != nil { http.Error(w, "Sign-in unavailable", http.StatusServiceUnavailable) return } http.SetCookie(w, &http.Cookie{ Name: "__Host-jarvis-flow", Value: binding, Path: "/", Secure: true, HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: 300, }) w.Header().Set("Cache-Control", "no-store") http.Redirect(w, r, target, http.StatusSeeOther) }}
// Call only from the exact registered callback handler, before creating a// product session. Reject ambiguous cookies; Finish consumes state atomically.func FinishSignIn(client *auth.Client, r *http.Request) (*auth.Session, error) { var bindings []string for _, cookie := range r.Cookies() { if cookie.Name == "__Host-jarvis-flow" { bindings = append(bindings, cookie.Value) } } if len(bindings) != 1 { return nil, auth.ErrFlow } return client.Finish(r.Context(), bindings[0], r.URL.Query())}The start path is your application route, not an endpoint supplied by Jarvis.
Mount it as GET. Mount the finish function inside the exact registered GET callback.
After FinishSignIn succeeds:
- Resolve the product account by
session.Identity.IssuerandSubject. - Persist tokens only in encrypted backend storage; never serialize them to JS.
- Rotate/create the product session cookie and expire the temporary flow cookie.
- Redirect to a fixed or allowlisted product-relative destination.
Set Cache-Control: no-store on callback/session responses. Add your application’s
Origin/CSRF protection to mutations. On a failed exchange, restart sign-in; do not
replay an ambiguously consumed callback.
Create a personal-context client
Section titled “Create a personal-context client”package integration
import ( "context" "errors"
"gitlab.com/jarvis-ecosystem/jarvisapis/sdk/go/auth" "gitlab.com/jarvis-ecosystem/jarvisapis/sdk/go/personal")
// session is already verified and loaded from YOUR backend session store.func PersonalClient(session *auth.Session, clientID string) (*personal.Client, error) { return personal.New(personal.Config{ BaseURL: "https://jarvis-api-dev.k8s1.aviproduction.org/external/v1", Issuer: session.Identity.Issuer, ClientID: clientID, Subject: session.Identity.Subject, AccessToken: func(context.Context) (string, error) { if !session.Tokens.Valid() { return "", errors.New("reauthentication required") } return session.Tokens.AccessToken, nil }, })}Only build this from a verified backend session. Do not accept its subject, issuer, client ID or token from a browser request. Signing in has granted no personal context yet: next, request separate consent.
Handle identity errors
Section titled “Handle identity errors”| SDK error | What your application does |
|---|---|
auth.ErrConfig |
Fix trusted configuration; do not bypass validation. |
auth.ErrFlow |
Reject the callback and start a new flow. |
auth.ErrDenied |
Return the user to your signed-out product state. |
auth.ErrToken |
Reject identity; never trust decoded claims alone. |
auth.ErrProvider |
Show a recoverable sign-in failure; no token fallback. |
An identity sign-in denial is different from a personal-context denial. The latter must preserve an already established product session.