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_IDandTEAMS_CLI_TENANT_IDtake 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 logoutkeeps the saved IDs. To send a profile back to the OSO app, delete itsclient_idandtenant_idlines, or runteams config set profiles.<profile>.client_id nulland the same fortenant_id(config setrewrites 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