Skip to content

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-inWhat you sendGood for
TokenThe generated token ir1.<client id>.<secret>Scripts, scheduled jobs, Power BI, Claude Code, VS Code. Simplest.
CertificateA short JWT you sign with the certificate’s private key, at most one hour validAutomation where no long-lived secret may be stored, and where certificates are already managed
Entra IDAn Entra ID access token for the “IntensiveReports API” appPeople 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.

Terminal window
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.

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 }
  • iss is the client id shown in Settings > API access.
  • aud is https://<your IntensiveReports host>/ir-api. The same value works for the API and the MCP server.
  • Supported signatures: RS256 and PS256 (RSA keys), ES256 (ECDSA keys).
  • The JWT may live at most one hour (exp minus iat). 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:

Terminal window
$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.

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:

Terminal window
$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_token
Invoke-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.

Every refusal is a JSON body with one sentence in error:

StatuserrorWhat to check
401Send Authorization: Bearer ir1.<client id>.<secret>, or a certificate client's signed assertion.The header is missing or doesn’t start with Bearer .
401The 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.
429Too 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).