Authentication
Every call to the API or the MCP server carries one header:
Authorization: Bearer <token>What goes in <token> depends on how the administrator set up your client in Settings > API access (the Sign-in choice). A client uses exactly one of three methods:
| Sign-in | What you send | Good for |
|---|---|---|
| Token | The generated token ir1.<client id>.<secret> | Scripts, scheduled jobs, Power BI, Claude Code, VS Code. Simplest. |
| Certificate | A short JWT you sign with the certificate’s private key, at most one hour valid | Automation where no long-lived secret may be stored, and where certificates are already managed |
| Entra ID | An Entra ID access token for the “IntensiveReports API” app | People signing in with their own account (Claude.ai, Claude Desktop, Claude Code), and apps using Entra client credentials |
Whatever the method, what the client may read is the same: its grant per product area and its list of hidden reports in Settings > API access. The sign-in only proves who is calling.
The administrator creates the client and copies the token from the dialog. This is the only time the token is shown. IntensiveReports keeps only a hash of it.
curl -H "Authorization: Bearer $IR_TOKEN" https://reports.example.com/ir-api/v1/reports- The token stops working when the client is switched off, deleted or past its expiry date (Expires after (days), 0 = never).
- New token in the client list issues a new secret. The old token stops working at once.
- Keep the token out of source code and logs. Use an environment variable or your platform’s secret store.
Certificate
Section titled “Certificate”For a certificate client, the administrator uploads the public certificate (.cer, .crt or .pem, RSA or ECDSA key) instead of generating a token. IntensiveReports never sees the private key. Your client proves it holds the key by signing a short-lived JWT and sending that as the bearer token:
// header{ "alg": "RS256", "typ": "JWT", "x5t#S256": "<optional: the certificate's SHA-256 thumbprint, base64url>" }
// payload{ "iss": "<client id>", "aud": "https://reports.example.com/ir-api", "iat": 1761734400, "exp": 1761736200 }issis the client id shown in Settings > API access.audishttps://<your IntensiveReports host>/ir-api. The same value works for the API and the MCP server.- Supported signatures:
RS256andPS256(RSA keys),ES256(ECDSA keys). - The JWT may live at most one hour (
expminusiat). Two minutes of clock difference are tolerated. Make a new one before it expires.
The setup folder of IntensiveReports contains New-IrApiClientAssertion.ps1, which makes such a JWT from a certificate in the Windows certificate store or a .pfx file:
$jwt = ./New-IrApiClientAssertion.ps1 -ClientId abc123 -BaseUrl https://reports.example.com -Thumbprint 3F2A...Invoke-RestMethod https://reports.example.com/ir-api/v1/reports -Headers @{ Authorization = "Bearer $jwt" }It works in PowerShell 7 and Windows PowerShell 5.1, signs for 30 minutes by default (-LifetimeMinutes up to 60) and prints only the token.
Entra ID
Section titled “Entra ID”With Entra ID sign-in, people and apps use their Entra identity instead of a shared token. It needs a second app registration in your tenant, IntensiveReports API. It holds no Graph permissions; it only issues the tokens. The administrator sets it up once with setup/New-IrApiResourceApp.ps1 (see API and MCP access). The script creates:
- the scope
api://<API app id>/Reports.Read, for people signing in; - the app role
IR.Api.Access. Only users, groups and apps that hold this role get a token at all.
Then, in Settings > API access, the administrator adds a client with Sign-in: Entra ID (user, group or app) for each user, group or app. A token is accepted when:
- it was issued by your tenant for the IntensiveReports API app;
- it holds the role
IR.Api.Access; - for a person: it contains the scope
Reports.Read; - a client in Settings > API access is bound to the caller itself, or to one of its security groups.
People don’t handle tokens themselves: MCP clients such as Claude.ai, Claude Desktop and Claude Code open the Microsoft sign-in page and keep the token. See Connect an AI assistant.
Apps use the client-credentials flow of their own app registration, asking for the scope api://<API app id>/.default:
$tenant = '<your tenant id>'$body = @{ client_id = '<your app id>' client_secret = $env:APP_SECRET scope = 'api://<IntensiveReports API app id>/.default' grant_type = 'client_credentials'}$token = (Invoke-RestMethod "https://login.microsoftonline.com/$tenant/oauth2/v2.0/token" -Method Post -Body $body).access_tokenInvoke-RestMethod https://reports.example.com/ir-api/v1/reports -Headers @{ Authorization = "Bearer $token" }The app must hold the IR.Api.Access role on the IntensiveReports API app (New-IrApiResourceApp.ps1 -AssignApp <app id>). It also needs a client in Settings > API access bound to it. Entra ID access tokens usually last about an hour; ask for a new one when it expires.
The audit log names both the client and the signed-in user or app.
When sign-in fails
Section titled “When sign-in fails”Every refusal is a JSON body with one sentence in error:
| Status | error | What to check |
|---|---|---|
401 | Send Authorization: Bearer ir1.<client id>.<secret>, or a certificate client's signed assertion. | The header is missing or doesn’t start with Bearer . |
401 | The token is unknown, expired or switched off. | Wrong or replaced token, client switched off or expired, certificate JWT expired or with the wrong aud, or an Entra token without the role or without a bound client. |
404 on every address | (empty) | The API is switched off in Settings > API access. |
429 | Too many requests: wait n s. | The rate limit; wait the seconds in the Retry-After header. Without a valid token the limit is lower (20 a minute per address by default). |
For security, a 401 doesn’t say which check failed. The administrator can look up every refused call, with its time, address and reason, in the audit log on the server (App_Data/api-audit, one file per day).