Skip to main content

Multi-Tenant Profiles

Profiles separate tenant and app settings.

[default]
profile = "client-a"

[profiles.client-a]
auth_app = "oso"
tenant_id = "client-a.onmicrosoft.com"
auth_flow = "device-code"
scopes = "User.Read Chat.ReadWrite ChatMessage.Send offline_access"

[profiles.locked-down]
auth_app = "byo"
client_id = "11111111-1111-1111-1111-111111111111"
tenant_id = "22222222-2222-2222-2222-222222222222"
scopes = "User.Read People.Read Files.Read.All offline_access"

Run commands with --profile client-a.

Saving a BYO app to a profile​

Instead of editing the file, sign in once with the app's IDs. From v0.9.0 a successful delegated login (browser or device code) writes --client-id and --tenant-id into the profile, editing only those two keys, so comments and other settings stay as they were:

teams --profile locked-down auth login --device-code \
--client-id 11111111-1111-1111-1111-111111111111 \
--tenant-id 22222222-2222-2222-2222-222222222222

# Later logins for the profile reuse the saved app
teams --profile locked-down auth login --device-code

The login prints where each ID came from on stderr (from the command line, from the environment, saved in profile 'locked-down' or built-in default), and its JSON output carries client_id, tenant_id and saved_to_config. A failed login saves nothing, and a failed write is a warning rather than a failed login.

  • TEAMS_CLI_CLIENT_ID and TEAMS_CLI_TENANT_ID take precedence over the profile but are never saved, so pass the IDs that way to sign in through an app once without changing the profile.
  • A client credentials login saves nothing: its confidential app would otherwise be used by the profile's next delegated login.
  • teams auth logout keeps the saved IDs. To send a profile back to the OSO app, delete its client_id and tenant_id lines, or run teams config set profiles.<profile>.client_id null and the same for tenant_id (config set rewrites the file and drops its comments).

teams auth list shows which account each profile actually holds, decoded from the stored token with no network call:

teams auth list
Profile User Tenant ID Auth Expires
* client-a ops@client-a.com 22222222-2222-2222-2222-222222222222 delegated 2026-08-29T18:04:11+00:00
locked-down svc@client-b.com 33333333-3333-3333-3333-333333333333 delegated 2026-08-29T17:52:03+00:00

The active profile is marked *. Piped or with --output json, each entry is an object:

{
"profiles": [
{ "name": "client-a", "user": "ops@client-a.com", "tenant_id": "...", "auth_type": "delegated", "expires_at": "..." }
],
"active": "client-a"
}

profiles is an array of objects, not of names — read .data.profiles[].name for the name alone. A profile whose token cannot be read or decoded is still listed, with the other fields null, so a broken credential entry does not hide it. An app-only profile has no user. No refresh is attempted, so expires_at may already be in the past.

Scope resolution is predictable: --scopes or TEAMS_CLI_SCOPES, then the selected profile's scopes. Login and consent URL generation fall back to the built-in defaults. A plain auth refresh instead reuses the stored token's granted scopes when no override or profile scopes are set, so refresh does not accidentally narrow an existing session. offline_access is added automatically for delegated flows.

After an admin grants consent to new scopes, refresh the stored token without a browser prompt:

teams auth refresh --profile locked-down