Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

## Unreleased

### Added

- `--redirect-uri`, `TEAMS_CLI_REDIRECT_URI` and the profile's `redirect_uri` select the browser login's redirect URI for `auth login`, `auth consent-url` and `auth doctor`. The login listens on the URI's host and port (both loopback addresses for `localhost`) for a request to the URI's path carrying the login's `state`; the flag is saved to the profile with the IDs. A redirect URI without a port gets a free port from the OS at each login, as MSAL does; the identity platform allows this because it ignores the port of a localhost redirect URI, so `--redirect-uri http://localhost` works for an app registered that way.

### Changed

- The default redirect URI is now `http://localhost/callback` on a free port instead of `http://localhost:8400/callback`, so a busy port 8400 no longer blocks login. App registrations with `http://localhost:8400/callback` keep working because the port is ignored.
- Updated Rust dependencies (`tokio` 1.54.1, `serde_json` 1.0.154, `uuid` 1.28.0, `webbrowser` 1.2.5) and the CI and release workflows' `Swatinem/rust-cache` pin to v2.9.3. `rust-version` in `Cargo.toml` is now 1.89, the oldest Rust the dependencies build with; it said 1.75.
- Updated `sha2` to 0.11.1 (it computes the PKCE code challenge for browser login). The challenge is now unit-tested against the RFC 7636 example, so a hashing or encoding change cannot slip through as failed logins.

### Fixed

- The browser login keeps serving its loopback redirect URI until the callback arrives. It served one connection, so a browser's speculative connection or a second request could leave it waiting forever or failing with "Failed to receive auth code from callback". A redirect carrying the identity platform's `error` (a declined consent, say) now fails the login at once with `error` and `error_description` (exit code 3) instead of that generic message, requests to other paths or with another `state` are ignored, and the login gives up after 10 minutes with no callback.
- Updated `rustls-webpki` to 0.103.16 and `webpki-roots` to 1.0.11 for RUSTSEC-2026-0382 and RUSTSEC-2026-0381, under which certificates issued by distrusted intermediates could still be accepted. The `tokio` update also clears RUSTSEC-2026-0386 (unsound `AsyncFd` constructors).

## v0.10.0 - 2026-10-15
Expand Down
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ The CLI has OSO's multi-tenant delegated public client app baked in:
```text
Client ID: fba1b5d0-fdd0-4fe2-9729-9ccdc38f9595
Default authority: organizations
Loopback redirect URI: http://localhost:8400/callback
Loopback redirect URI: http://localhost/callback (any port)
```

Customer admin consent:
Expand Down Expand Up @@ -200,11 +200,14 @@ teams auth login --device-code --scopes "User.Read ChannelMessage.Read.All offli
# Browser-based login with a customer-owned app
teams auth login --client-id <client-id> --tenant-id <tenant-id>

# Browser login with an app whose registered redirect URI is http://localhost (no path)
teams auth login --client-id <client-id> --tenant-id <tenant-id> --redirect-uri http://localhost

# Device code flow with a customer-owned app
teams auth login --device-code --client-id <client-id> --tenant-id <tenant-id>
```

A delegated login saves `--client-id` and `--tenant-id` to the profile once it
A delegated login saves `--client-id`, `--tenant-id` and `--redirect-uri` to the profile once it
succeeds, so the profile's next `teams auth login` reuses that app registration
instead of the built-in one. Environment variables and client credentials
logins are never saved; see [docs/auth.md](docs/auth.md).
Expand Down Expand Up @@ -254,6 +257,14 @@ Delegated scope resolution follows the same order: `--scopes` (or
`TEAMS_CLI_SCOPES`), then the profile's `scopes` field, then the built-in
default scope set.

The browser login's redirect URI follows it too: `--redirect-uri` (or
`TEAMS_CLI_REDIRECT_URI`), then the profile's `redirect_uri`, then
`http://localhost/callback`. The CLI listens on the URI's host and port for
the callback on the URI's path, and the URI must be registered on the app.
A URI without a port gets a free port from the OS at each login; the identity
platform ignores the port of a localhost redirect URI, so the registered URI
needs none.

When permissions are consented after login (for example an admin grants a new
delegated scope), `teams auth refresh` silently redeems the stored refresh
token for the resolved scopes — no browser or device code. Refresh tokens are
Expand Down Expand Up @@ -716,6 +727,7 @@ auth_flow = "device-code"
| `TEAMS_CLI_CLIENT_ID` | Azure AD application (client) ID |
| `TEAMS_CLI_CLIENT_SECRET` | Azure AD client secret |
| `TEAMS_CLI_TENANT_ID` | Azure AD tenant ID |
| `TEAMS_CLI_REDIRECT_URI` | Redirect URI for the browser login (same precedence as `--redirect-uri`; not saved to the profile) |
| `TEAMS_CLI_PROFILE` | Named credential profile (overridden by `--profile`; an explicit value beats the config's `default.profile`) |
| `TEAMS_CLI_SCOPES` | Delegated OAuth scopes for login (same precedence as `--scopes`; ignored by client credentials) |
| `TEAMS_CLI_ACCESS_TOKEN` | Pre-obtained Microsoft Graph access token (skips login entirely; `--profile`/`TEAMS_CLI_PROFILE` do not affect which token is used) |
Expand Down
22 changes: 14 additions & 8 deletions docs/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Delegated login defaults to OSO's multi-tenant public client app:
```text
Client ID: fba1b5d0-fdd0-4fe2-9729-9ccdc38f9595
Authority: organizations
Redirect URI: http://localhost:8400/callback
Redirect URI: http://localhost/callback (any port)
```

The default lets most users start with:
Expand Down Expand Up @@ -210,11 +210,12 @@ teams auth login --device-code \
--tenant-id <customer-tenant-id>
```

After a successful delegated login (browser or device code), a client ID or
tenant ID given with `--client-id` or `--tenant-id` is saved to the profile's
`client_id` and `tenant_id` in the config file. The next `teams auth login` for that profile signs in through the
After a successful delegated login (browser or device code), a client ID,
tenant ID or redirect URI given with `--client-id`, `--tenant-id` or
`--redirect-uri` is saved to the profile's `client_id`, `tenant_id` and
`redirect_uri` in the config file. The next `teams auth login` for that profile signs in through the
same registration without being told again, instead of falling back to the
built-in app. Only those two values change; comments, layout and every other
built-in app. Only those values change; comments, layout and every other
setting in the file are left as they were, and a symlinked config file stays
a symlink. A login that fails saves nothing, and a value given again replaces
the saved one. Saving is best effort: if the file cannot be written, the
Expand Down Expand Up @@ -283,16 +284,21 @@ teams --profile customer auth login --device-code
teams --profile customer auth doctor --output json
```

Instead of editing the file, a delegated login given `--client-id` and
`--tenant-id` writes the profile's `client_id` and `tenant_id` once it
Instead of editing the file, a delegated login given `--client-id`,
`--tenant-id` and `--redirect-uri` writes the profile's `client_id`,
`tenant_id` and `redirect_uri` once it
succeeds, leaving the rest of the file as it was (see "Customer-owned
delegated app" above).

BYO delegated app requirements:

- Supported account type: usually single-tenant for locked-down enterprises.
- Public client flows enabled.
- Redirect URI for browser flow: `http://localhost:8400/callback`.
- Redirect URI for browser flow: `http://localhost/callback` on a port the OS picks, by default.
`--redirect-uri`, `TEAMS_CLI_REDIRECT_URI` or the profile's `redirect_uri`
change it. Entra ignores the port on loopback redirect URIs but matches the
path exactly, so an app registered with `http://localhost` needs
`--redirect-uri http://localhost`.
- Delegated Graph permissions required for the features the customer wants.
- Admin consent granted where required.

Expand Down
15 changes: 12 additions & 3 deletions docs/man/teams-auth.7
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Delegated login defaults to the OSO multi-tenant public client app:
.nf
Client ID: fba1b5d0-fdd0-4fe2-9729-9ccdc38f9595
Authority: organizations
Redirect URI: http://localhost:8400/callback
Redirect URI: http://localhost/callback (any port)
.fi
.PP
Common login commands:
Expand Down Expand Up @@ -62,6 +62,14 @@ field replaces the default delegated scope string for that profile;
is appended when missing. Use it when a BYO app has admin-consented
permissions beyond the defaults.
.PP
An app whose registered redirect URI has no path, such as
.B http://localhost,
needs
.B redirect_uri = "http://localhost"
in the profile or
.B --redirect-uri
at login.
.PP
Then run:
.PP
.nf
Expand All @@ -70,9 +78,10 @@ teams --profile customer auth doctor --output json
.fi
.PP
Instead of editing the file, a delegated login given
.B --client-id
and
.B --client-id,
.B --tenant-id
and
.B --redirect-uri
saves them to the profile once it succeeds, leaving the rest of the file as
it was. Values from
.B TEAMS_CLI_CLIENT_ID
Expand Down
14 changes: 14 additions & 0 deletions docs/man/teams-config.5
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ Delegated OAuth scope string requested at login for this profile. Replaces the
built-in default delegated scopes;
.B offline_access
is appended when missing. Ignored by client credentials login.
.TP
.B redirect_uri
Redirect URI for the browser login. The CLI listens on its host and port,
which must be localhost or a loopback address, and sends it to the identity
platform, where it must match the app registration. Default
http://localhost/callback. A URI without a port gets a free port, which the
identity platform allows for localhost.
.SH RESOLUTION ORDER
Profile resolution:
.nf
Expand Down Expand Up @@ -173,6 +180,13 @@ Delegated scope resolution:
3. Built-in default delegated scopes
.fi
.PP
Redirect URI resolution:
.nf
1. --redirect-uri flag or TEAMS_CLI_REDIRECT_URI
2. Selected config profile redirect_uri
3. http://localhost/callback
.fi
.PP
Access token resolution for normal commands:
.nf
1. TEAMS_CLI_ACCESS_TOKEN
Expand Down
28 changes: 22 additions & 6 deletions docs/man/teams.1
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ Set page size for paginated list commands.
Follow Graph pagination and return all pages.
.SH AUTHENTICATION
.nf
teams auth login [--client-credentials] [--device-code] [--client-id ID] [--client-secret SECRET] [--tenant-id ID] [--scopes SCOPES]
teams auth login [--client-credentials] [--device-code] [--client-id ID] [--client-secret SECRET] [--tenant-id ID] [--redirect-uri URI] [--scopes SCOPES]
teams auth refresh [--scopes SCOPES]
teams auth status
teams auth consent-url [--client-id ID] [--tenant-id ID] [--scopes SCOPES]
teams auth doctor [--client-id ID] [--tenant-id ID]
teams auth consent-url [--client-id ID] [--tenant-id ID] [--redirect-uri URI] [--scopes SCOPES]
teams auth doctor [--client-id ID] [--tenant-id ID] [--redirect-uri URI]
teams auth list
teams auth switch NAME
teams auth logout [--profile NAME] [--all]
Expand All @@ -78,10 +78,13 @@ registrations can still be selected with
.B --client-id
and
.B --tenant-id.
A successful delegated login saves those two flags to the profile's
.B client_id
A successful delegated login saves those two flags, and
.B --redirect-uri,
to the profile's
.B client_id,
.B tenant_id
and
.B tenant_id,
.B redirect_uri,
so the profile's next login reuses them; values from the environment, and a
client credentials login, are never saved.
Client credentials always require explicit customer app credentials.
Expand All @@ -96,6 +99,15 @@ or
then the profile
.B scopes
field, then the built-in default delegated scopes.
The browser login redirect URI is
.B --redirect-uri
or
.B TEAMS_CLI_REDIRECT_URI,
then the profile
.B redirect_uri
field, then
.B http://localhost/callback.
A URI without a port gets a free port from the operating system.
.B teams auth refresh
silently redeems the stored refresh token for the resolved scopes, picking up
newly admin-consented permissions without a browser; without an explicit
Expand Down Expand Up @@ -397,6 +409,10 @@ whose value shows in process listings and shell history.
.B TEAMS_CLI_TENANT_ID
Azure AD tenant ID.
.TP
.B TEAMS_CLI_REDIRECT_URI
Redirect URI for the browser login. Same precedence as
.B --redirect-uri.
.TP
.B TEAMS_CLI_PROFILE
Named credential profile. Overridden by
.B --profile.
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstarts/enterprise-admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Confirm:
For a customer-owned app:

- Create an Entra app registration.
- Configure public client redirect URI `http://localhost:8400/callback`.
- Configure public client redirect URI `http://localhost/callback`.
- Enable public client flows.
- Add delegated Graph permissions required by your chosen command set.
- Grant admin consent.
Expand Down
2 changes: 1 addition & 1 deletion docs/release-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Known live behavior:
Entra app registration status as of 2026-05-27:

- OSO public client app is multi-tenant (`AzureADMultipleOrgs`).
- Public client redirect URI is configured as `http://localhost:8400/callback`.
- Public client redirect URI is configured as `http://localhost/callback`.
- Public client/device-code fallback is enabled.
- Implicit grant access-token and ID-token issuance are disabled.
- Home tenant admin consent has been granted for the current delegated scope bundle.
Expand Down
18 changes: 13 additions & 5 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Check:
- Tenant allows user consent or admin consent has been granted.
- The OSO app is allowed by enterprise app policy.
- BYO profile has `auth_app = "byo"` and a valid `client_id`.
- Browser PKCE redirect `http://localhost:8400/callback` is allowed.
- Browser PKCE redirect URI (default `http://localhost/callback`) is registered on the app.
- Device-code login is used for SSH or remote terminals.

Useful command:
Expand Down Expand Up @@ -203,19 +203,27 @@ In headless Linux CI, prefer one of:

## Browser login callback fails

Browser login uses PKCE and the redirect URI:
Browser login uses PKCE and, by default, the redirect URI:

```text
http://localhost:8400/callback
http://localhost/callback
```

on a free port the OS picks. `--redirect-uri` selects another registered one,
for example `--redirect-uri http://localhost` for an app registered with
`http://localhost`; a URI with a port fixes the port (for a firewall rule, say).

Check:

- Port `8400` is free.
- If the redirect URI names a port, that port is free.
- Local firewall allows loopback.
- BYO app registration includes that redirect URI.
- BYO app registration includes that redirect URI. `AADSTS50011` means the URI is not registered on the app: register it, or pass the registered one.
- Use `--device-code` if a browser callback is not possible.

If you decline consent or the identity platform refuses the sign-in, the
login fails at once with its `error` and `error_description` (exit code 3).
With no callback at all, it gives up after 10 minutes.

## Rate limiting

Exit code `6` means rate limited after retries.
Expand Down
Loading
Loading