Skip to content
Open
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
83 changes: 32 additions & 51 deletions examples/graph/README.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,51 @@
# Auth test
# Graph Example

Run this first to get all the config files:
Demonstrates Microsoft Graph access from a Teams bot using both user-delegated and app-only permissions.

```
teams config add atk.oauth
```

Then run via ATK.
## Commands

## Teams Toolkit Configuration: Oauth
| Command | Description |
|---------|-------------|
| Any message | Triggers SSO sign-in, then shows your profile |
| `/signout` | Sign out of your account |
| `/app-users` | List org users via `app.getAppGraph()` (no sign-in needed) |

Use this if you want to enable user authentication in your Teams application.
## Setup

## How to update scopes
1. Create an app with SSO:

1. In the `aad.manifest.json` file, update the `requiredResourceAccess` list to add the required scopes.
```
teams app create
teams app user-auth sso setup <app-id>
```

2. In the `infra/botRegistration/azurebot.bicep` file, under the `botServicesMicrosoftGraphConnection` resource, update the `properties.scopes` string to be a comma-delimited list of the required scopes.
2. Run the bot:

### Example
```
teams app bot start
```

If you want to add the `People.Read.All` and `User.ReadBasic.All` scopes.
## Adding Scopes

1. Your `requiredResourceAccess` property should look like:
To request additional Graph permissions, edit the SSO connection:

```json
"requiredResourceAccess": [
{
"resourceAppId": "Microsoft Graph",
"resourceAccess": [
{
"id": "People.Read.All",
"type": "Scope"
}
]
},
{
"resourceAppId": "Microsoft Graph",
"resourceAccess": [
{
"id": "User.ReadBasic.All",
"type": "Scope"
}
]
},
]
```
teams app user-auth sso edit <app-id> --connection-name graph --scopes "User.Read,People.Read.All"
```

2. Update the `properties.scopes` to be `People.Read.All,User.ReadBasic.All`.
For app-only permissions (e.g., `User.Read.All` for `/app-users`), grant them in Azure Portal > App registrations > API permissions and have an admin consent.

## Configuring a Regional Bot
NOTE: This example uses west europe, but follow the equivalent for other locations.
## Regional Bot

1. In `azurebot.bicep`, replace all `global` occurrences to `westeurope`
2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com`
2. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect`
3. In `index.ts`, update `AppOptions` to include `apiClientSettings`
To use a regional token endpoint (e.g., Europe), update `validDomains` in your manifest to include `europe.token.botframework.com` and set `apiClientSettings` in your app:

```typescript
const app = new App({
oauth: {
defaultConnectionName: 'graph',
},
logger: new ConsoleLogger('@examples/auth', { level: 'debug' }),
apiClientSettings: {
oauthUrl: "https://europe.token.botframework.com",
}
oauth: {
defaultConnectionName: 'graph',
},
apiClientSettings: {
oauthUrl: 'https://europe.token.botframework.com',
}
});
```
```
22 changes: 22 additions & 0 deletions examples/graph/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ app.message('/signout', async ({ send, signout, isSignedIn }) => {
await send('you have been signed out!');
});

app.message('/app-users', async ({ send }) => {
try {
const graph = app.getAppGraph();
const users = await graph.call(endpoints.users.list);

if (users?.value?.length) {
const userList = users.value.slice(0, 5).map(
(u, i) => `**${i + 1}.** ${u.displayName ?? 'N/A'} (${u.userPrincipalName ?? 'N/A'})`
).join('\n\n');
await send(`**Organization Users**\n\n${userList}`);
} else {
await send('No users found.');
}
} catch (e) {
await send(
`Failed to list users: ${e}\n\n` +
'Ensure the app has **User.Read.All** application permission granted ' +
'in Azure Portal > App registrations > API permissions, and that an admin has consented.'
);
}
});

app.on('message', async ({ log, signin, userGraph, isSignedIn }) => {
if (!isSignedIn) {
await signin({
Expand Down
29 changes: 24 additions & 5 deletions packages/apps/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ export type AppActivityOptions = {
export class App<TPlugin extends IPlugin = IPlugin> {
readonly api: ApiClient;
readonly cloud: CloudEnvironment;
readonly graph: GraphClient;
readonly log: ILogger;
readonly server: HttpServer;
readonly http?: HttpPlugin;
Expand All @@ -203,6 +202,14 @@ export class App<TPlugin extends IPlugin = IPlugin> {
return this.tokenManager.credentials;
}

/**
* A Microsoft Graph client using the app's default tenant.
* @deprecated Use {@link getAppGraph}() instead. This getter always uses the app's default tenant. `getAppGraph(tenantId?)` supports multi-tenant scenarios.
*/
get graph(): GraphClient {
return this.getAppGraph();
}

/**
* the apps id
*/
Expand Down Expand Up @@ -309,10 +316,6 @@ export class App<TPlugin extends IPlugin = IPlugin> {
this.cloud
);

this.graph = new GraphClient(
this.client.clone({ token: () => this.getAppGraphToken() })
);

// initialize TokenManager with credentials
this.tokenManager = new TokenManager({
clientId: this.options.clientId,
Expand Down Expand Up @@ -545,6 +548,22 @@ export class App<TPlugin extends IPlugin = IPlugin> {
return res;
}

/**
* Get a Microsoft Graph client configured with the app's token.
*
* @remarks
* This client can be used for app-only operations that don't require user context.
* For multi-tenant apps, pass a tenantId to get a tenant-specific token.
*
* @param tenantId - Optional tenant ID. If not provided, uses the app's default tenant.
* @returns A GraphClient for app-only Graph operations.
*/
getAppGraph(tenantId?: string) {
return new GraphClient(
this.client.clone({ token: () => this.getAppGraphToken(tenantId) })
);
}

/**
* subscribe to an event
* @param name event to subscribe to
Expand Down
1 change: 1 addition & 0 deletions packages/apps/src/contexts/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface IBaseActivityContextOptions<T extends Activity = Activity> {

/**
* the app graph client
* @deprecated Use `app.getAppGraph(tenantId?)` instead for tenant-specific Graph access.
*/
appGraph: GraphClient;

Expand Down
1 change: 1 addition & 0 deletions packages/apps/src/contexts/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface IFunctionContext<T = any> extends IClientContext {

/**
* the app graph client
* @deprecated Use `app.getAppGraph(tenantId?)` instead for tenant-specific Graph access.
*/
appGraph: GraphClient;

Expand Down
Loading