User Sign-Ins

Posted by

As published in https://techcommunity.microsoft.com/t5/Azure-Active-Directory-Identity/Users-can-now-check-their-sign-in-history-for-unusual-activity/ba-p/916066, Microsoft offers visibility into the sign-ins for Azure AD users. This view is specifically crafted for end users with explanations and a reduced set of information. But what if you are interested in the full amount of sign-in information, like you get in the administrator’s view? As a normal end user, without any administrative roles, you have the permission to read this information. Quote from https://docs.microsoft.com/en-us/azure/active-directory/reports-monitoring/concept-sign-ins: “Who can acess the data? … Any user (non-admins) can access their own sign-ins.

But how?

Well, for one thing, you can use Azure Portal and navigate to the sign-ins for your own Azure AD user identity.

Obviously, navigating through the Azure Portal presupposes that you are allowed to do just that, which is usually not the case for normal end users without administrative roles. So, what then? Well, Microsoft Graph API comes to the rescue. After all, you are allowed to read your sign-in data and preventing you from accessing the Azure Portal should not prevent you from accessing all pieces of information from your sign-ins. To leverage the Graph API to access you sign-in data, you need two pieces of data:

  • the Tenant ID of the Azure Active Directory tenant that holds your Azure AD identity,
  • the Object ID of your Azure AD identity.

Here is how you can find out about the Tenant ID and the Azure AD identity’s Obejct ID.

To read the sign-ins using the Graph API, the version 1.0 as well as the beta version documentation states that AuditLog.Read.All, Directory.Read.All permissions are needed to call this API. A normal end user does not have those permissions. Another dead end? No. It turns out you need to filter appropriately to read only your own sign-ins, then you don’t need those permissions. Here is the trick:

Call the Graph API as follows:

"https://graph.microsoft.com/beta/auditLogs/signIns?api-version=beta&$filter=(userId eq 'YourObjectIDgoeshere')"

For example, with "https://graph.microsoft.com/beta/auditLogs/signIns?api-version=beta&$filter=(userId eq 'd9349202-2a5a-40bf-89c6-fe630ffb5861')" as seen in Graph Explorer:

The following PowerShell script does that (see the highlighted line below). You need to modify the script with your Tenant ID and your Azure AD account’s Object ID at the beginning; the script uses the Powershell Client ID and requires you to enter your credentials, including MFA if your tenant is, hopefully, set up for it. The script writes the sign-ins into a csv file. Note that you must have the Azure AD Powershell module or the Azure AD Preview Powershell module installed.

Download the Powershell script Get-AzureADSignIns-MSGraph.ps1 as zip file.


#------------------------------------------------------------
# Based on Azure AD Sign-Ins "DownloadScripts.ps1" from Microsoft
#
# Function Get-AccessToken taken from https://blogs.technet.microsoft.com/cloudlojik/2018/06/29/connecting-to-microsoft-graph-with-a-native-app-using-powershell/
# (Paul Kotylo)
#
#
# 20191208
# Stephan Wälde
#
# tested with version 2.0.2.77 of the AzureADPreview PowerShell module
# tested with version 2.0.2.76 of the AzureAD Powershell module
#------------------------------------------------------------


# Replace with own Tenant ID and Account ID
$tenantId = "xxxxxxxx-86f1-41af-91ab-xxxxxxxxxxxx"
$accountId = "xxxxxxxx-44b4-4489-8e99-xxxxxxxxxxxx"





# The filter expression in this URL gets the Sign-In records of the specified Account ID
# The records will be returned by the Graph API after Account ID's logon without any additional permissions
$url = "https://graph.microsoft.com/beta/auditLogs/signIns?api-version=beta&`$filter=(userId%20eq%20%27$accountId%27)&`$orderby=createdDateTime%20desc"



# from https://blogs.technet.microsoft.com/cloudlojik/2018/06/29/connecting-to-microsoft-graph-with-a-native-app-using-powershell/
# using PowerShell as clientID
Function Get-AccessToken ($TenantName, $ClientID, $redirectUri, $resourceAppIdURI, $CredPrompt){
    Write-Host "Checking for AzureAD module..."
    if (!$CredPrompt){$CredPrompt = 'Auto'}

    $AadModule = Get-Module -Name "AzureAD" -ListAvailable
    if ($AadModule -eq $null) {$AadModule = Get-Module -Name "AzureADPreview" -ListAvailable}
    if ($AadModule -eq $null) {write-host "AzureAD Powershell module is not installed. The module can be installed by running 'Install-Module AzureAD' or 'Install-Module AzureADPreview' from an elevated PowerShell prompt. Stopping." -f Yellow;exit}
    if ($AadModule.count -gt 1) {
        $Latest_Version = ($AadModule | select version | Sort-Object)[-1]
        $aadModule      = $AadModule | ? { $_.version -eq $Latest_Version.version }
        $adal           = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms      = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
        }
    else {
        $adal           = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
        $adalforms      = Join-Path $AadModule.ModuleBase "Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll"
        }
    [System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
    [System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
    $authority          = "https://login.microsoftonline.com/$TenantName"
    $authContext        = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
    $platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters"    -ArgumentList $CredPrompt
    $authResult         = $authContext.AcquireTokenAsync($resourceAppIdURI, $clientId, $redirectUri, $platformParameters).Result
    return $authResult
    }






# First, let's authenticate
$clientId = "1b730954-1685-4b74-9bfd-dac224a7b894" # PowerShell
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$MSGraphURI = "https://graph.microsoft.com"
$CredPrompt = "Always"

$AccessToken = Get-AccessToken -TenantName $tenantId -ClientID $clientId -redirectUri $redirectUri -resourceAppIdURI $MSGraphURI -CredPrompt $CredPrompt






# Next, let's get the Sign-in data
$now = get-date -Format "yyyyMMddhhmm"
$outputFile = "$now-AzureADSignIns-$tenantId.csv"

$headers = @{
        'Content-Type'  = 'application\json'
        'Authorization' = $AccessToken.CreateAuthorizationHeader()
        }


# anything below is taken from the "DownloadScripts.ps1" from Microsoft
Function Expand-Collections {
    [cmdletbinding()]
    Param (
        [parameter(ValueFromPipeline)]
        [psobject]$MSGraphObject
    )
    Begin {
        $IsSchemaObtained = $False
    }
    Process {
        If (!$IsSchemaObtained) {
            $OutputOrder = $MSGraphObject.psobject.properties.name
            $IsSchemaObtained = $True
        }

        $MSGraphObject | ForEach-Object {
            $singleGraphObject = $_
            $ExpandedObject = New-Object -TypeName PSObject

            $OutputOrder | ForEach-Object {
                Add-Member -InputObject $ExpandedObject -MemberType NoteProperty -Name $_ -Value $(($singleGraphObject.$($_) | Out-String).Trim())
            }
            $ExpandedObject
        }
    }
    End {}
}
        
Write-Output "--------------------------------------------------------------"
Write-Output "Downloading from $url"
Write-Output "Output file: $outputFile"
Write-Output "--------------------------------------------------------------"

$count=0
$retryCount = 0
$oneSuccessfulFetch = $False

Do {
    Write-Output "Fetching data using Url: $url"

    Try {
        $myReport = (Invoke-WebRequest -UseBasicParsing -Headers $headers -Uri $url)
        $convertedReport = ($myReport.Content | ConvertFrom-Json).value
        $convertedReport | Expand-Collections | ConvertTo-Csv -NoTypeInformation | Add-Content $outputFile
        $url = ($myReport.Content | ConvertFrom-Json).'@odata.nextLink'
        $count = $count+$convertedReport.Count
        Write-Output "Total Fetched: $count"
        $oneSuccessfulFetch = $True
        $retryCount = 0
    }
    Catch [System.Net.WebException] {
        $statusCode = [int]$_.Exception.Response.StatusCode
        Write-Output $statusCode
        Write-Output $_.Exception.Message
        if($statusCode -eq 401 -and $oneSuccessfulFetch)
        {
            # Token might have expired! Renew token and try again
            $authResult = $authContext.AcquireToken($MSGraphURI, $clientId, $redirectUri, "Auto")
            $token = $authResult.AccessToken
            $headers = Get-Headers($token)
            $oneSuccessfulFetch = $False
        }
        elseif($statusCode -eq 429 -or $statusCode -eq 504 -or $statusCode -eq 503)
        {
            # throttled request or a temporary issue, wait for a few seconds and retry
            Start-Sleep -5
        }
        elseif($statusCode -eq 403 -or $statusCode -eq 400 -or $statusCode -eq 401)
        {
            Write-Output "Please check the permissions of the user"
            break;
        }
        else {
            if ($retryCount -lt 5) {
                Write-Output "Retrying..."
                $retryCount++
            }
            else {
                Write-Output "Download request failed. Please try again in the future."
                break
            }
        }
     }
    Catch {
        $exType = $_.Exception.GetType().FullName
        $exMsg = $_.Exception.Message

        Write-Output "Exception: $_.Exception"
        Write-Output "Error Message: $exType"
        Write-Output "Error Message: $exMsg"

         if ($retryCount -lt 5) {
            Write-Output "Retrying..."
            $retryCount++
        }
        else {
            Write-Output "Download request failed. Please try again in the future."
            break
        }
    }

    Write-Output "--------------------------------------------------------------"
} while($url -ne $null)
	

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s