Authentication and Session
The tk-ota-push CLI implements a Web-to-CLI authentication flow secured by PKCE (Proof Key for Code Exchange, S256) in AuthManager (lib/src/cli/auth/auth_manager.dart) and persists credentials locally via SessionManager (lib/src/cli/auth/session_manager.dart). For headless CI/CD environments, the OTA_API_KEY environment variable bypasses the interactive browser flow.
1. Interactive Login (tk-ota-push login)
# Standard login (reads web_console_url from shorebird.yaml or defaults to http://localhost:3000)
tk-ota-push login
# Login with an explicit Web Console URL
tk-ota-push login --web-url https://console.ota.example.org
Web URL Resolution Cascade
When tk-ota-push login is invoked, the Web Console URL is resolved in the following order:
--web-urlCLI flagweb_console_urlfield inshorebird.yamlOTA_WEB_CONSOLE_URLenvironment variable- Default:
http://localhost:3000
Detailed PKCE Flow Sequence (AuthManager.login)
- Initialization: The CLI starts a local loopback listener (
http://127.0.0.1:<port>). - Browser Login: The CLI opens your default browser at
/cli/loginin the TkushPatch Console. - Token Exchange: Upon successful login, a secure session token is sent back to the loopback listener.
- Credential Storage: Session credentials are encrypted and stored in
~/.tk_ota_push/credentials.json.
Exact query parameters sent in the initial authorization URL opened in the browser:
callback_url:http://127.0.0.1:<port>/callbackstate: 32-character cryptographically random string (Random.secure())code_challenge: SHA-256 digest encoded in Base64URL without=paddingcode_challenge_method:S256device_name:Platform.localHostnamecli_version:0.2.0
The maximum wait timeout before the login attempt expires is 3 minutes.
2. Token Exchange and Refresh
Initial Token Exchange (/api/cli/token/exchange)
Once the temporary code is received on /callback, the CLI sends a POST request to <webConsoleUrl>/api/cli/token/exchange:
{
"code": "<ephemeral_code>",
"code_verifier": "<64_char_verifier>"
}
Automatic Refresh (ensureFreshToken)
Before executing release, patch, promote, or symbols, if OTA_API_KEY is not set in the environment, the CLI loads the local session and checks credentials.isExpired:
- A token is considered expired 30 seconds before its
expires_attimestamp (DateTime.now().isAfter(expiresAt.subtract(const Duration(seconds: 30)))). - If expired and a
refreshTokenis present,AuthManager.ensureFreshTokensends aPOSTrequest totokenRefreshUrl(or<webConsoleUrl>/api/cli/token/refresh):
{
"refresh_token": "<refresh_token>",
"tenant_id": "<tenant_id>"
}
If the server responds with 200 OK, the refreshed credentials are immediately saved to ~/.tk_ota_push/credentials.json.
3. Local Session Storage (SessionManager)
The session file is stored at:
- macOS / Linux:
$HOME/.tk_ota_push/credentials.json(with permissions restricted viachmod 600) - Windows:
%USERPROFILE%\.tk_ota_push\credentials.json
JSON structure persisted by CliCredentials.toJson():
{
"access_token": "<jwt_access_token>",
"refresh_token": "<jwt_refresh_token>",
"expires_at": "2026-11-09T14:30:00.000Z",
"tenant_id": "sara-money",
"username": "alice.dev@saramoney.cm",
"sub": "user-uuid-999",
"roles": ["RELEASE_DEVELOPER", "SECURITY_OFFICER"],
"token_refresh_url": "http://localhost:3000/api/cli/token/refresh",
"web_console_url": "http://localhost:3000"
}
4. Inspecting the Active Session (tk-ota-push whoami)
The whoami command reads ~/.tk_ota_push/credentials.json and displays identity details without making any network request:
tk-ota-push whoami
Output:
Connecté en tant que : alice.dev@saramoney.cm
Tenant ID : sara-money
Subject (UUID) : user-uuid-999
Rôles : RELEASE_DEVELOPER, SECURITY_OFFICER
Expiration jeton : 2026-11-09 15:30:00.000
5. Headless CI/CD Authentication (OTA_API_KEY)
On a continuous integration server (GitHub Actions, GitLab CI, Jenkins, Bitbucket Pipelines), do not run tk-ota-push login. Instead, inject the access token via the OTA_API_KEY environment variable:
export OTA_API_KEY="<YOUR_CI_OR_BEARER_TOKEN>"
export OTA_SERVER_URL="https://ota.example.org"
export OTA_TENANT_ID="sara-money"
tk-ota-push release --platform android --artifact aab
In OtaApiClient, whenever apiKey is non-empty, the following HTTP header is attached to every request sent to /api/admin/v1/*:
Authorization: Bearer <OTA_API_KEY>
6. Logging Out (tk-ota-push logout)
To clear the local session and delete ~/.tk_ota_push/credentials.json:
tk-ota-push logout
Possible outputs:
Session supprimée avec succès.(if a session file existed)Déjà déconnecté.(if nocredentials.jsonfile was present)