Skip to content

Commit 52c4ce8

Browse files
committed
Reduce default delegated auth scopes
1 parent 52110e2 commit 52c4ce8

7 files changed

Lines changed: 30 additions & 11 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ teams auth login
175175
# Device code flow with OSO's public client app
176176
teams auth login --device-code
177177

178+
# Channel message reads require a broader Graph scope that often needs admin approval
179+
teams auth login --device-code --scopes "User.Read ChannelMessage.Read.All offline_access"
180+
178181
# Browser-based login with a customer-owned app
179182
teams auth login --client-id <client-id> --tenant-id <tenant-id>
180183

@@ -204,6 +207,12 @@ teams auth login --client-credentials \
204207

205208
Tokens are cached in the OS keyring — subsequent commands reuse the session without re-authentication.
206209

210+
Default delegated login asks for chat, channel-send, discovery, user lookup,
211+
and presence scopes. It intentionally does not request
212+
`ChannelMessage.Read.All`, because Microsoft marks that delegated scope as
213+
admin-consent required. Use `--scopes` or a customer-owned app when a workflow
214+
needs channel message reads.
215+
207216
**Credential resolution order**: CLI flags > environment variables > config file profiles.
208217

209218
### Why not import Teams client tokens?

docs/auth-implementation-plan.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,13 @@ Recommended initial delegated scopes:
135135
- `Team.ReadBasic.All`
136136
- `Channel.ReadBasic.All`
137137
- `ChannelMessage.Send`
138-
- `ChannelMessage.Read.All`
139138
- `Chat.ReadWrite`
140139
- `ChatMessage.Send`
141140
- `ChatMessage.Read`
142141
- `User.ReadBasic.All`
143142
- `Presence.Read.All`
144143

145-
Implementation note: split these into documented scope presets before release. The current default scope string is broad and convenient for testing, but commercial onboarding should explain exactly why each scope exists and allow lower-scope profiles where possible.
144+
Implementation note: keep the default scope string below known admin-consent-required delegated scopes where possible. `ChannelMessage.Read.All` is needed for channel message reads, but it should be requested explicitly with `--scopes` or through a customer-owned app because Microsoft marks it as admin-consent required.
146145

147146
### 2. Customer BYO Public Client App
148147

docs/auth.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,20 @@ offline_access
7676
Team.ReadBasic.All
7777
Channel.ReadBasic.All
7878
ChannelMessage.Send
79-
ChannelMessage.Read.All
8079
Chat.ReadWrite
8180
ChatMessage.Send
8281
ChatMessage.Read
8382
User.ReadBasic.All
8483
Presence.Read.All
8584
```
8685

87-
These permissions cover the current read/write message, team/channel discovery, chat, user lookup, and presence smoke tests. Future features may need additional consent.
86+
These permissions cover the current chat read/write, channel-send, team/channel discovery, user lookup, and presence smoke tests. The default does not include `ChannelMessage.Read.All` because Microsoft marks that delegated Graph scope as admin-consent required. Add it explicitly when a workflow needs channel message reads:
87+
88+
```bash
89+
teams auth login --device-code --scopes "User.Read ChannelMessage.Read.All offline_access"
90+
```
91+
92+
Future features may need additional consent.
8893

8994
## Login options
9095

docs/faq.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,14 @@ offline_access
4444
Team.ReadBasic.All
4545
Channel.ReadBasic.All
4646
ChannelMessage.Send
47-
ChannelMessage.Read.All
4847
Chat.ReadWrite
4948
ChatMessage.Send
5049
ChatMessage.Read
5150
User.ReadBasic.All
5251
Presence.Read.All
5352
```
5453

55-
Customers should review these during admin consent. Future features may require additional permissions.
54+
The default avoids `ChannelMessage.Read.All` because Microsoft marks that delegated Graph scope as admin-consent required. Customers that need channel message reads should grant it explicitly with `--scopes` or through a customer-owned app. Future features may require additional permissions.
5655

5756
## Why did `team list` return no teams?
5857

src/auth/auth_code_pkce.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use std::sync::Mutex;
88
use tokio::sync::oneshot;
99

1010
use super::token::MsTokenResponse;
11+
use crate::config::DEFAULT_DELEGATED_SCOPES;
1112
use crate::error::{Result, TeamsError};
1213

13-
const DEFAULT_SCOPES: &str = "User.Read Team.ReadBasic.All Channel.ReadBasic.All ChannelMessage.Send ChannelMessage.Read.All Chat.ReadWrite ChatMessage.Send ChatMessage.Read User.ReadBasic.All Presence.Read.All offline_access";
1414
const REDIRECT_URI: &str = "http://localhost:8400/callback";
1515

1616
fn random_urlsafe_bytes(len: usize) -> Result<String> {
@@ -37,7 +37,7 @@ pub async fn authenticate(
3737
tenant_id: &str,
3838
scopes: Option<&str>,
3939
) -> Result<MsTokenResponse> {
40-
let scopes = scopes.unwrap_or(DEFAULT_SCOPES);
40+
let scopes = scopes.unwrap_or(DEFAULT_DELEGATED_SCOPES);
4141
let (verifier, challenge) = generate_pkce()?;
4242

4343
let state = random_urlsafe_bytes(16)?;

src/auth/device_code.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ use serde::Deserialize;
33
use std::time::Duration;
44

55
use super::token::MsTokenResponse;
6+
use crate::config::DEFAULT_DELEGATED_SCOPES;
67
use crate::error::{Result, TeamsError};
78

8-
const DEFAULT_SCOPES: &str = "User.Read Team.ReadBasic.All Channel.ReadBasic.All ChannelMessage.Send ChannelMessage.Read.All Chat.ReadWrite ChatMessage.Send ChatMessage.Read User.ReadBasic.All Presence.Read.All offline_access";
9-
109
#[derive(Debug, Deserialize)]
1110
struct DeviceCodeResponse {
1211
device_code: String,
@@ -40,7 +39,7 @@ pub async fn authenticate(
4039
tenant_id: &str,
4140
scopes: Option<&str>,
4241
) -> Result<MsTokenResponse> {
43-
let scopes = scopes.unwrap_or(DEFAULT_SCOPES);
42+
let scopes = scopes.unwrap_or(DEFAULT_DELEGATED_SCOPES);
4443
let http = Client::new();
4544

4645
// Step 1: Request device code

src/config.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::error::{Result, TeamsError};
88

99
pub const OSO_PUBLIC_CLIENT_ID: &str = "fba1b5d0-fdd0-4fe2-9729-9ccdc38f9595";
1010
pub const DEFAULT_DELEGATED_TENANT_ID: &str = "organizations";
11+
pub const DEFAULT_DELEGATED_SCOPES: &str = "User.Read Team.ReadBasic.All Channel.ReadBasic.All ChannelMessage.Send Chat.ReadWrite ChatMessage.Send ChatMessage.Read User.ReadBasic.All Presence.Read.All offline_access";
1112

1213
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1314
pub struct ConfigFile {
@@ -404,6 +405,13 @@ auth_flow = "device-code"
404405
);
405406
}
406407

408+
#[test]
409+
fn default_delegated_scopes_avoid_admin_required_channel_read() {
410+
assert!(DEFAULT_DELEGATED_SCOPES.contains("ChatMessage.Send"));
411+
assert!(DEFAULT_DELEGATED_SCOPES.contains("ChannelMessage.Send"));
412+
assert!(!DEFAULT_DELEGATED_SCOPES.contains("ChannelMessage.Read.All"));
413+
}
414+
407415
#[test]
408416
fn delegated_auth_byo_requires_client_id() {
409417
let mut config = ConfigFile::default();

0 commit comments

Comments
 (0)