@extends('layouts.app') @section('header')

Microsoft integration — {{ $company->name }}

This company's own Entra tenant. Nothing here is shared with any other company.
@if ($company->ms_verified_at) Credentials OK · {{ $company->ms_verified_at->diffForHumans() }} @elseif ($configured) Not tested yet @else Incomplete @endif
@endsection @section('content') @foreach (['success' => true, 'warning' => false] as $key => $good) @if (session($key))
{{ session($key) }}
@endif @endforeach {{-- Step-by-step result of the last ping, held in the flash so it survives the redirect back. Absent on a normal page load, which is why the card is not rendered as an empty shell. --}} @if ($ping = session('msPing'))

Last ping

{{ number_format($ping['ms']) }}ms
@endif
@csrf @method('PUT')

App registration

@error('ms_client_id')
{{ $message }}
@enderror
The company's own tenant GUID, or common for a multi-tenant app. @error('ms_tenant_id')
{{ $message }}
@enderror
The secret value, not the secret ID. Encrypted at rest. Leaving this blank keeps the one already stored. @error('ms_client_secret')
{{ $message }}
@enderror
The account Teams messages appear to come from. Optional — the ping checks it resolves in the directory when it is set. @error('ms_bot_email')
{{ $message }}
@enderror
Only needed for user sign-in. It must match a URI registered on the app registration exactly, host and spelling included. @error('ms_redirect_uri')
{{ $message }}
@enderror

Test the connection

Checks the client ID, secret and tenant against Entra and throws the token away. Read-only: nothing is sent and no message is posted.

It cannot check Graph access. Every call this integration makes is delegated — made as the sending account — so those permissions only exist once that account has signed in and consented.

@csrf
@unless ($configured) Client ID, tenant ID and secret must all be saved first. @endunless

What the tenant needs

On the customer's app registration, under API permissions — all delegated, none needing admin consent:

  • openid, profile, email — sign the account in
  • offline_access — keep it signed in without a person present
  • User.Read — read the sending account's own profile
  • User.ReadBasic.All — resolve a recipient by email address
  • Chat.Create, Chat.ReadWrite, ChatMessage.Read, ChatMessage.Send — open a 1:1 chat and post to it

No application permissions, and specifically not User.Read.All. An app-only token cannot post to a 1:1 chat as a person, which is the entire point of sending as the bot account.

@endsection