Skip to content
Jarvis home →
Docs / SDK guides / Go · backend sign-in

Go · backend sign-in

The official server-side relying-party client. Keep code exchange, tokens and session custody on your backend.

GO 1.25+SDK 0.2.0-alpha.1Updated Sep 9, 2026
Terminal
GOPRIVATE=gitlab.com/jarvis-ecosystem/* \
go get gitlab.com/jarvis-ecosystem/jarvisapis/sdk/go@v0.2.0-alpha.1

GitLab 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.go
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.

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 ExpiresAt and 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.

signin.go
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:

  1. Resolve the product account by session.Identity.Issuer and Subject.
  2. Persist tokens only in encrypted backend storage; never serialize them to JS.
  3. Rotate/create the product session cookie and expire the temporary flow cookie.
  4. 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.

personal.go
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.

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.