Skip to content

feat: exchange OIDC identity tokens for an API key in rsconnect login#802

Merged
nealrichardson merged 5 commits into
mainfrom
claude-issue-800
Jun 26, 2026
Merged

feat: exchange OIDC identity tokens for an API key in rsconnect login#802
nealrichardson merged 5 commits into
mainfrom
claude-issue-800

Conversation

@nealrichardson

@nealrichardson nealrichardson commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #800.

Adds OIDC identity-token exchange to the rsconnect login command, so trusted publishers (e.g. GitHub Actions) can obtain a Connect API key without interactive OAuth login.

How it works

  1. rsconnect login <server> --identity-token <oidc-token> (or --identity-token-file <path>, or the CONNECT_IDENTITY_TOKEN / CONNECT_IDENTITY_TOKEN_FILE env vars). Use --identity-token - to read from stdin; prefer the file/env forms to keep the secret out of process arguments and CI logs.
  2. Discovers the server's OAuth metadata to confirm token-exchange support and resolve the correct token endpoint (preserving any path prefix and query).
  3. Performs an RFC 8693 token exchange (grant_type=token-exchange, subject_token_type=id_token, requested_token_type=access_token). Connect verifies the token against a configured trusted publisher and mints a short-lived API key.
  4. Validates the key (test_server + test_api_key) and stores it as the server credential, honoring --name, --insecure, --cacert, and --no-set-default. Subsequent deploy/list/etc. then just work.

Failure responses are mapped to actionable errors: unsupported token exchange, connection/TLS errors, and invalid_grant (ambiguous match, verification failure, no match).

Testing

  • New tests in tests/test_oauth.py cover metadata discovery, the exchange success/error paths, endpoint preservation, connection-error handling, and the CLI flag / file / stdin / empty-token behavior.
  • black and flake8 clean on changed files; pyright reports only pre-existing errors.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-26 15:58 UTC

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
7356 6046 82% 0% 🟢

New Files

No new covered files...

Modified Files

File Coverage Status
rsconnect/main.py 81% 🟢
rsconnect/oauth.py 74% 🟢
TOTAL 78% 🟢

updated for commit: 35ecbd2 by action🐍

@nealrichardson nealrichardson requested a review from atheriel June 26, 2026 14:52

@atheriel atheriel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes complete sense to do.

Comment thread rsconnect/oauth.py Outdated
Comment thread rsconnect/main.py
Comment thread docs/CHANGELOG.md Outdated
nealrichardson added a commit that referenced this pull request Jun 26, 2026
- Rename --token to --identity-token to disambiguate from other token
  types; add --identity-token-file plus CONNECT_IDENTITY_TOKEN and
  CONNECT_IDENTITY_TOKEN_FILE env vars so the plaintext token need not
  appear in process args or CI/CD logs.
- Preflight the exchange via OAuth discovery: confirm the server supports
  the token-exchange grant (grant_types_supported) and use the discovered
  token endpoint (which also carries any path prefix), instead of relying
  on a 404 from a blind POST.
- Generalize wording away from "trusted publishing" since this flag can
  also serve identity federation.
- Fold the rsconnect login features into a single CHANGELOG bullet.

Addresses review feedback from @atheriel on #802.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nealrichardson and others added 4 commits June 26, 2026 11:44
Wrap Connect's trusted-publishing auth flow as a CLI command. Passing
`rsconnect login <server> --token <oidc-token>` exchanges an OIDC token
(e.g. a GitHub Actions OIDC token) for a short-lived Connect API key via
an RFC 8693 token exchange against `/oauth/v1/token`, then validates and
saves the key as the server credential. Use `--token -` to read the
token from stdin.

Closes #800

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
exchange_token_for_api_key() stripped the server URL to scheme://netloc
before POSTing to /oauth/v1/token, which broke Connect servers configured
behind a path prefix (e.g. https://host/connect). Pass the full URL to
HTTPServer so the token-exchange path is appended relative to any prefix.

Found by roborev review (job 58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
exchange_token_for_api_key() read response.status without first checking
response.exception. On a network/TLS failure HTTPServer.request() returns
an HTTPResponse with no status set, so the access raised AttributeError
(an internal error) instead of an actionable RSConnectException. Check
response.exception first and raise with connection context, matching the
pattern in RSConnectClient.

Found by roborev review (job 59).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename --token to --identity-token to disambiguate from other token
  types; add --identity-token-file plus CONNECT_IDENTITY_TOKEN and
  CONNECT_IDENTITY_TOKEN_FILE env vars so the plaintext token need not
  appear in process args or CI/CD logs.
- Preflight the exchange via OAuth discovery: confirm the server supports
  the token-exchange grant (grant_types_supported) and use the discovered
  token endpoint (which also carries any path prefix), instead of relying
  on a 404 from a blind POST.
- Generalize wording away from "trusted publishing" since this flag can
  also serve identity federation.
- Fold the rsconnect login features into a single CHANGELOG bullet.

Addresses review feedback from @atheriel on #802.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@atheriel atheriel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😎

exchange_token_for_api_key() posted to only the path of the discovered
token_endpoint, dropping any params/query. If a server advertises a token
endpoint with a query component, the exchange would hit the wrong URL.
Reconstruct the full request target (path, params, query) via urlunparse.

Found by roborev review (job 65).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nealrichardson nealrichardson changed the title feat: login --token for OIDC trusted-publishing token exchange feat: exchange OIDC identity tokens for an API key in rsconnect login Jun 26, 2026
@nealrichardson nealrichardson enabled auto-merge (squash) June 26, 2026 15:52
@nealrichardson nealrichardson merged commit aafef56 into main Jun 26, 2026
23 checks passed
@nealrichardson nealrichardson deleted the claude-issue-800 branch June 26, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rsconnect login with OIDC token

2 participants