--- title: "Register a Microsoft Graph Application" description: "Azure portal walkthrough for creating the app registration that powers the Teams meeting pipeline" --- # Register a Microsoft Graph Application The Teams meeting pipeline reads meeting transcripts, recordings, and related artifacts from Microsoft Graph using **app-only** (daemon) authentication — no user sign-in, no interactive consent per meeting. That requires an Azure AD application registration with admin-consented application permissions. This guide walks through: 1. Creating the app registration 2. Creating a client secret 3. Granting the Graph API permissions the pipeline needs 4. Admin-consenting those permissions 5. (Optional) Scoping the app to specific users with an Application Access Policy You need **tenant admin rights** (or an admin to grant consent on your behalf) to finish this. Bookmark the values you collect — they go into `~/.hermes/.env` at the end. ## Prerequisites - A Microsoft 365 tenant with Teams Premium or Teams licenses that produce meeting transcripts and recordings - Admin access to the Azure portal at [entra.microsoft.com](https://entra.microsoft.com) - A publicly reachable HTTPS endpoint for Graph change notifications (set up later, in the webhook listener step) ## Step 1: Create the App Registration 1. Sign in to [entra.microsoft.com](https://entra.microsoft.com) as a tenant admin. 2. Navigate to **Identity → Applications → App registrations**. 3. Click **New registration**. 4. Fill in: - **Name:** `Hermes Teams Meeting Pipeline` (or any name you'll recognize). - **Supported account types:** *Accounts in this organizational directory only (Single tenant)*. - **Redirect URI:** leave blank — app-only auth does not need one. 5. Click **Register**. You'll land on the app's overview page. Copy two values: - **Application (client) ID** → `MSGRAPH_CLIENT_ID` - **Directory (tenant) ID** → `MSGRAPH_TENANT_ID` ## Step 2: Create a Client Secret 1. In the left nav, open **Certificates & secrets**. 2. Click **New client secret**. 3. **Description:** `hermes-graph-secret`. **Expires:** pick a value that matches your rotation policy (6-24 months is typical). 4. Click **Add**. 5. Copy the **Value** column immediately — it's only shown once. That value is `MSGRAPH_CLIENT_SECRET`. > The **Secret ID** column is not the secret. You want the **Value** column. ## Step 3: Grant Graph API Permissions The pipeline uses a minimum-viable set of application permissions. Add only what you need; each one widens what the app can read tenant-wide. 1. In the left nav, open **API permissions**. 2. Click **Add a permission** → **Microsoft Graph** → **Application permissions**. 3. Add the permissions from the table below that match what you want the pipeline to do. 4. After adding, click **Grant admin consent for ``**. The Status column should flip to a green checkmark for every permission. ### Required for transcript-first summaries | Permission | What it lets the app do | |------------|--------------------------| | `OnlineMeetings.Read.All` | Read Teams online meeting metadata (subject, participants, join URL). | | `OnlineMeetingTranscript.Read.All` | Read meeting transcripts generated by Teams. | ### Required for recording fallback (when a transcript is unavailable) | Permission | What it lets the app do | |------------|--------------------------| | `OnlineMeetingRecording.Read.All` | Download Teams meeting recordings for offline STT processing. | | `CallRecords.Read.All` | Resolve meetings from call records when only the join URL is known. | ### Required for outbound summary delivery (Graph mode only) If `platforms.teams.extra.delivery_mode` is `graph`, the pipeline posts summaries into a Teams channel or chat via the Graph API. Skip these if you use `incoming_webhook` delivery mode instead. | Permission | What it lets the app do | |------------|--------------------------| | `ChannelMessage.Send` | Post messages into Teams channels on behalf of the app. | | `Chat.ReadWrite.All` | Post messages into 1:1 and group chats (only if you set `chat_id` as the delivery target). | ### Not recommended - `OnlineMeetings.ReadWrite.All` / `Chat.ReadWrite` without `.All` — broader than the pipeline needs. - Delegated permissions — the pipeline uses app-only (client-credentials) flow; delegated permissions won't work without user sign-in. ## Step 4: (Recommended) Scope the App with an Application Access Policy By default, application permissions like `OnlineMeetings.Read.All` grant the app access to **every** meeting in the tenant. For partner demos and dev tenants that's fine; for production you almost certainly want to restrict which users' meetings the app can read. Microsoft provides **Application Access Policies** for Teams exactly for this. The policy is a PowerShell-only surface; there's no portal UI for it. From an admin PowerShell with the MicrosoftTeams module installed and connected (`Connect-MicrosoftTeams`): ```powershell # Create a policy scoped to the Hermes app New-CsApplicationAccessPolicy ` -Identity "Hermes-Meeting-Pipeline-Policy" ` -AppIds "" ` -Description "Restrict Hermes meeting pipeline to allow-listed users" # Grant the policy to specific users whose meetings the pipeline may read Grant-CsApplicationAccessPolicy ` -PolicyName "Hermes-Meeting-Pipeline-Policy" ` -Identity "alice@example.com" Grant-CsApplicationAccessPolicy ` -PolicyName "Hermes-Meeting-Pipeline-Policy" ` -Identity "bob@example.com" ``` Propagation can take up to 30 minutes after granting. Verify with: ```powershell Test-CsApplicationAccessPolicy -Identity "alice@example.com" -AppId "" ``` Without the policy, **any** user's meetings are readable — that's what the permission technically grants. Don't skip this step on a production tenant. ## Step 5: Write the Credentials to Your Env File Put the three values you collected into `~/.hermes/.env`: ```bash MSGRAPH_TENANT_ID= MSGRAPH_CLIENT_ID= MSGRAPH_CLIENT_SECRET= ``` Set file permissions so only you can read the secret: ```bash chmod 600 ~/.hermes/.env ``` ## Step 6: Verify the Token Flow Hermes ships a Graph auth smoke-test. From your Hermes install: ```python python -c " import asyncio from tools.microsoft_graph_auth import MicrosoftGraphTokenProvider provider = MicrosoftGraphTokenProvider.from_env() token = asyncio.run(provider.get_access_token()) print('Token acquired, length:', len(token)) print(provider.inspect_token_health()) " ``` A successful run prints a long token string and a health dict showing `cached: True` and an `expires_in_seconds` value near 3600. Failures produce a `MicrosoftGraphTokenError` with the Azure error code — the most common are: | Azure error | Meaning | Fix | |-------------|---------|-----| | `AADSTS7000215: Invalid client secret` | Secret value mismatched or expired. | Generate a new secret in step 2; update `.env`. | | `AADSTS700016: Application not found` | Wrong `MSGRAPH_CLIENT_ID` or wrong tenant. | Double-check the values from step 1 are from the same app. | | `AADSTS90002: Tenant not found` | Typo in `MSGRAPH_TENANT_ID`. | Copy the Directory (tenant) ID from the app overview again. | | `insufficient_claims` at call time (not token time) | Token acquires but Graph returns 401/403. | You skipped step 3 admin-consent, or added permissions but haven't re-consented. Revisit API permissions and click **Grant admin consent** again. | ## Rotating the Client Secret Azure client secrets have a hard expiry. Before yours expires: 1. Create a second client secret in step 2 without deleting the first one. 2. Update `MSGRAPH_CLIENT_SECRET` in `~/.hermes/.env` with the new value. 3. Restart the gateway so the new secret is picked up: `hermes gateway restart`. 4. Verify with the smoke test above. 5. Delete the old secret from the Azure portal. ## Next Steps Once credentials verify cleanly, continue with: - **Webhook listener setup** — stand up the `msgraph_webhook` gateway platform that receives Graph change notifications. - **Pipeline configuration** — configure the Teams meeting pipeline runtime and operator CLI. - **Outbound delivery** — wire summaries back into a Teams channel or chat. Those pages land alongside the PRs that add the corresponding runtime. This credentials setup is a standalone prerequisite and is safe to complete in advance.