InTouch Hub · Blue Isle Software

Microsoft Entra Sign-in & Audit Logs

Read Entra sign-in logs, directory audit logs and provisioning events through Microsoft Graph. Sign-in logs require Entra ID P1 or P2.

Provided free and as is, without warranty of any kind — including merchantability, fitness for a particular purpose, and the accuracy or completeness of any result. See the licence. You are responsible for checking what this produces before relying on it.

auditidentitymsgraphreviewschedulestatus

Microsoft Entra Sign-in & Audit Logs Tool

Read Entra sign-in logs, directory audit logs and provisioning events. For a security review, a failed-sign-in sweep, or "who added this person to that group".

Tool ID

msgraph-signin-audit

Credential Required

Yes — the shared msgraph credential, owned by the msgraph connector. Configure the tenant once and all nine tools use it; rotating the client secret is one edit rather than nine.

It is a server-managed OAuth credential: InTouch mints and refreshes the access token itself and the connector never sees the secret. For unattended work set grant_type to client_credentials (the app-only grant) — an app-only token belongs to the application, so a 06:00 schedule does not run as a person, see only what that person can see, and stop working the day they leave.

Credential Properties

Property Type Default Description
client_id string Required. The Entra app registration's Application (client) ID.
client_secret string Required for app-only. The secret's Value, not its Secret ID.
token_url string Required. https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
grant_type string (blank) client_credentials for unattended app-only use. Blank means the user-delegated refresh-token flow.
scope string https://graph.microsoft.com/.default — resolves to whatever application permissions an admin has consented to.
access_token string Machine-managed. Leave empty; the server mints and persists it.
refresh_token string Delegated credentials only. Not used by the app-only grant.
expires_at string 0 Machine-managed expiry, epoch seconds.

Entra setup

  1. https://entra.microsoft.comApp registrations → your app → API permissions.
  2. Add a permissionMicrosoft GraphApplication permissionsnot Delegated. Picking Delegated is the most common failure: it consents cleanly and then 403s at runtime.
  3. Tick the permissions listed under Permissions below, then Grant admin consent. The Status column must read Granted.
  4. Permissions live in the token, not the portal. A token minted before the grant carries the old claims for about an hour — blank access_token to force a fresh mint.

Permissions

AuditLog.Read.All.

Operations

1. list_sign_ins — List sign-ins

Who authenticated, from where, and whether it succeeded. Requires Entra ID P1 or P2. Calls GET /auditLogs/signIns.

Properties:

Property Type Default Description
operation string Required. list_sign_ins
query string Optional. OData query params as a JSON object string. ALWAYS bound a listing by time — {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z", "$top": 200}. Failed sign-ins only: {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z and status/errorCode ne 0"}. Entra retains roughly 7 days without a premium tier, 30 days with one — older records do not exist to be queried.

Published Outputs: - result — the JSON response body - status — HTTP status code

2. get_sign_in — Get one sign-in

One sign-in record by id. Calls GET /auditLogs/signIns/{id}.

Properties:

Property Type Default Description
operation string Required. get_sign_in
id string Required. Log record id. Required by get_sign_in and get_directory_audit.
query string Optional. OData query params as a JSON object string. ALWAYS bound a listing by time — {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z", "$top": 200}. Failed sign-ins only: {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z and status/errorCode ne 0"}. Entra retains roughly 7 days without a premium tier, 30 days with one — older records do not exist to be queried.

Published Outputs: - result — the JSON response body - status — HTTP status code

3. list_directory_audits — List directory changes

Who changed what — role assignments, group membership, app consent. No premium tier needed. Calls GET /auditLogs/directoryAudits.

Properties:

Property Type Default Description
operation string Required. list_directory_audits
query string Optional. OData query params as a JSON object string. ALWAYS bound a listing by time — {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z", "$top": 200}. Failed sign-ins only: {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z and status/errorCode ne 0"}. Entra retains roughly 7 days without a premium tier, 30 days with one — older records do not exist to be queried.

Published Outputs: - result — the JSON response body - status — HTTP status code

4. get_directory_audit — Get one audit record

One directory audit record by id. Calls GET /auditLogs/directoryAudits/{id}.

Properties:

Property Type Default Description
operation string Required. get_directory_audit
id string Required. Log record id. Required by get_sign_in and get_directory_audit.
query string Optional. OData query params as a JSON object string. ALWAYS bound a listing by time — {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z", "$top": 200}. Failed sign-ins only: {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z and status/errorCode ne 0"}. Entra retains roughly 7 days without a premium tier, 30 days with one — older records do not exist to be queried.

Published Outputs: - result — the JSON response body - status — HTTP status code

5. list_provisioning — List provisioning events

Events from Entra's user-sync connectors. Calls GET /auditLogs/provisioning.

Properties:

Property Type Default Description
operation string Required. list_provisioning
query string Optional. OData query params as a JSON object string. ALWAYS bound a listing by time — {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z", "$top": 200}. Failed sign-ins only: {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z and status/errorCode ne 0"}. Entra retains roughly 7 days without a premium tier, 30 days with one — older records do not exist to be queried.

Published Outputs: - result — the JSON response body - status — HTTP status code

Paging

Graph answers a collection with 100 rows and an @odata.nextLink, so a naive listing returns a fraction of a large tenant with HTTP 200 and no sign anything is missing. This connector follows the continuation link until the collection is exhausted. If it stops at the page cap it says so — the result carries complete: false with a warning, and the step returns WARNING rather than SUCCESS. A page cap must never look like the end of the data.

Notes — two things that are not bugs

The two logs have different entitlements, which is why they are separate operations. list_sign_ins needs Entra ID P1 or P2; on a tenant without it the endpoint answers 403 forever, consent or no consent. list_directory_audits — the record of directory changes, such as a role assignment or a group membership edit — is available without a premium tier. So on a bare tenant this connector is half useful rather than useless, and it matters which half you ask for.

Retention is days, not years, and Graph is not an archive. Entra keeps roughly 7 days without a premium tier and 30 with one. "Every failed sign-in last quarter" has no answer here at any permission level — the tenant had to be exporting to Log Analytics, storage or a SIEM before the question was asked. Scheduling a nightly job through this connector is precisely how an organisation builds that history for itself.

Always bound the query. Sign-in logs are the highest-volume collection in Graph; an unfiltered listing pages until it hits the cap and reports complete: false, having spent minutes and a chunk of the tenant's throttling budget to return a prefix. {"$filter": "createdDateTime ge 2026-08-20T00:00:00Z", "$top": 200} — every time.

Self-Contained JAR

The jar bundles all runtime dependencies; only intouch-tool-api is provided by the server.

Publishes

The keys a later task may reference as {{taskName.key}}. Referencing anything not listed here resolves to nothing at run time.