# KeyLockr Delegated SSO Go Example

The recommended mobile flow keeps KeyLockr completely out of the third-party App and backend
network path:

1. The App calls its own backend `POST /kl/login/start`.
2. The App opens the returned `keylockr://sso-request?...` deep link and waits for its registered
   Universal Link/App Link callback.
3. The App sends the opaque backend assertion and its private `finish_secret` to its own backend
   `POST /kl/login/finish`.

The third-party backend does not call KeyLockr during finish. It verifies the KeyLockr seal and
authenticated box offline. The public `delegated.go` helper implements the signed request, strict
callback parser, assertion verification, finish-secret check, time bounds, and atomic completion
store contract. `testdata/delegated_sso_v1.json` is the canonical cross-language fixture.

## Backend Start

Use Go 1.23 or newer. Persist the signing and encryption private keys in backend secret storage;
register only their public keys and exact return URLs in MyDeveloper.

```go
start, err := StartDelegatedSSO(DelegatedStartOptions{
    AppTag:       "your-app-tag",
    ReturnURL:    "https://login.example.com/keylockr/callback",
    Capabilities: []string{"sso"},
}, serviceSigningPrivateKey)
if err != nil {
    return err
}

// Store start.Pending in Redis for 720 seconds. Never log the response body.
// Return Cache-Control: no-store plus these fields to the App:
// start.DeepLink, start.FinishSecret, start.RequestDigest, start.ExpiresAt.
```

`challenge` and `finish_secret` are independent 32-byte CSPRNG values. The challenge is public;
the finish secret is private caller proof. Both deadlines come from one wall-clock snapshot:
approval expires after 600 seconds and pending retention after 720 seconds.

## App Callback

Keep the finish secret and request digest in sensitive local pending state. Never place the finish
secret in a QR code, deep link, callback URL, log, crash report, or analytics event.

```go
callback, err := ParseDelegatedCallbackURL(callbackURL, pending.RequestDigest)
if err != nil {
    return err
}
if callback.Cancelled {
    // Delete only the App's local pending state. Do not call finish.
    return nil
}
// Send callback.BackendAssertion and the original finish_secret to your backend.
```

The parser rejects duplicate/unknown callback fields, digest mismatch, non-canonical base64url,
and every size-limit violation before finish. A callback alone never creates a session.

## Backend Finish

`CompleteDelegatedSSO` verifies the assertion offline and then calls the supplied completion store.
The store implementation must atomically move the Redis transaction from pending to completed,
return the same session for the same secret/assertion replay, and reject every mismatch. Do not
create a SQL approval/transaction table or call `/app_verify`.

```go
session, err := CompleteDelegatedSSO(
    ctx,
    redisCompletionStore,
    pending,
    finishSecret,
    backendAssertion,
    keyLockrSigningPublicKey,
    keyLockrEncryptionPublicKey,
    serviceEncryptionPrivateKey,
    time.Now(),
)
if err != nil {
    // Externally expose only a generic authentication failure.
    return err
}
```

Before activating `session`, every login client must show the verified nickname and full Safe ID
and require explicit confirmation. Use Safe ID as the stable external identity; nickname is display
text only. Keep an expected Safe ID baseline on the client and warn before switching accounts.

## Legacy Advanced Example

`main.go`, `kps.go`, and `appdata.go` retain the existing direct-client handshake for compatibility
with installed integrations. That legacy path uses KeyLockr HTTP, a temporary WebSocket, native
completion callbacks, optional AppData/AP access, and `/app_verify` where applicable. Delegated SSO
must never silently fall back to it.

```bash
env GOWORK=off go test ./...
go run . -app-tag YOUR_APP_TAG
```

Never log or upload private keys, finish secrets, assertions, complete callback URLs, the AP Account
Key, AppData filekeys, decrypted AppData, or plaintext credentials.
