Graph API – Search AuditLogs\SignIns for users to see who has used MFA within 30 Days


Scenario: You want to query the Azure AD SignIn Logs to see who has used MFA within the last 30 days via Graph and PowerShell. You have the Userprincipalnames in a CSV already.

Scriptlet:
Notes:
1. It performs a get-accesstoken function which can be found and loaded from this blog: Get an Access Token for Graph API via PowerShell – Ex-Shell


   #Declare global variables
   $i = import-csv "C:\temp\userprincipalnames.csv"  #With Userprincipalname as the column header 
   $start = get-date((get-date).adddays(-30)) -Format "yyyy-MM-dd"

   #Loop it
    $i.userprincipalname | Sort | %{
        #Declare UPN
        $n = $_
        "Checking $N"

         #Build the URI   
         $appuri = "https://graph.microsoft.com/v1.0/auditlogs/signIns?$('$filter')=(userprincipalname eq '$n') and (createdDateTime ge $start)"
         $appuri = ([System.Uri]$appuri).AbsoluteUri

        #Get the token and create the RestSplat
        $header = get-accesstoken 
        $results = @()
        $RestSplat = @{ 
            URI         = $appuri
            Headers     = $header
            Method      = 'GET' 
            ContentType = "application/json" 
            } 

        #Invoke the Rest URI
        $Tempresults =  Invoke-RestMethod @RestSplat


        #Play with results
            #MFA check    
            $Tempresults.value.appliedConditionalAccessPolicies | Where {($_.result -eq "Success") -and ($_.enforcedGrantControls -like "*MFA*")}

            #Signin at all?
           $Tempresults.value

    }


Graph API via PowerShell: Find Sign In Logs for Specific user and time

Scenario: You want to use Graph API to query the SignIn logs.

Scriptlet:
Notes:
1. It performs a get-accesstoken function which can be found and loaded from this blog: Get an Access Token for Graph API via PowerShell – Ex-Shell


#Sign-In Logs Varaibles, edit below:
$Start = "2021-10-01"
$End = "2021-10-05"
$appuri = "https://graph.microsoft.com/v1.0/auditlogs/signIns?$('$filter')=(userprincipalname eq 'steveman@superhero.com') and (createdDateTime ge $start) and (createdDateTime lt $end)"

#Clean the URI
$appuri = ([System.Uri]$appuri).AbsoluteUri

#Loop it for ALL SignIn logs
Do{
"$appUri"
$header = get-accesstoken 
$results = @()
$RestSplat = @{ 
    URI         = $appuri
    Headers     = $header
    Method      = 'GET' 
    ContentType = "application/json" 
} 
$Tempresults =  Invoke-RestMethod @RestSplat 
$results += $tempresults.value
$appuri = $tempresults."@odata.NextLink"
}While($appuri -ne $null)

#
$results |Select CreatedDateTime,ClientAppUsed,userprincipalname

Get an Access Token for Graph API via PowerShell

Scenario: You want to connect to Microsoft Graph API via PowerShell to pull in data so you can use it within PowerShell.

How to get an Azure Token:
First, this is an awesome article walking through A-Z on how to do this.
https://tech.nicolonsky.ch/explaining-microsoft-graph-access-token-acquisition/

Notes:
1. Make sure you give your Azure App the correct permissions for everything it will need.
2. I created a function to get the Access Token so I can call it anytime, see below. This may be a little older than what the article above references
3. When you can, use Certificates for authentication.

Function:
Edit the top 3 values; TenantName, AppID, and Certificate

function Get-AccessToken {

        $TenantName = "<whatever your tenant name is>"
        $AppId = "<whatever your app id is>"
        $Certificate = <Whatever your cert is,  example:  Get-Item Cert:\LocalMachine\My\CE0XXXXXXXXXXXXXXXXXXXXXXX>


#Acquire an Access Token (Using a Certificate) with Java Web Token (JWT)

$Scope = "https://graph.microsoft.com/.default"




# Create base64 hash of certificate
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash())

# Create JWT timestamp for expiration
$StartDate = (Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()
$JWTExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds
$JWTExpiration = [math]::Round($JWTExpirationTimeSpan,0)

# Create JWT validity start timestamp
$NotBeforeExpirationTimeSpan = (New-TimeSpan -Start $StartDate -End ((Get-Date).ToUniversalTime())).TotalSeconds
$NotBefore = [math]::Round($NotBeforeExpirationTimeSpan,0)

# Create JWT header
$JWTHeader = @{
    alg = "RS256"
    typ = "JWT"
    # Use the CertificateBase64Hash and replace/strip to match web encoding of base64
    x5t = $CertificateBase64Hash -replace '\+','-' -replace '/','_' -replace '='
}

# Create JWT payload
$JWTPayLoad = @{
    # What endpoint is allowed to use this JWT
    aud = "https://login.microsoftonline.com/$TenantName/oauth2/token"

    # Expiration timestamp
    exp = $JWTExpiration

    # Issuer = your application
    iss = $AppId

    # JWT ID: random guid
    jti = [guid]::NewGuid()

    # Not to be used before
    nbf = $NotBefore

    # JWT Subject
    sub = $AppId
    }


# Convert header and payload to base64
$JWTHeaderToByte = [System.Text.Encoding]::UTF8.GetBytes(($JWTHeader | ConvertTo-Json))
$EncodedHeader = [System.Convert]::ToBase64String($JWTHeaderToByte)

$JWTPayLoadToByte =  [System.Text.Encoding]::UTF8.GetBytes(($JWTPayload | ConvertTo-Json))
$EncodedPayload = [System.Convert]::ToBase64String($JWTPayLoadToByte)

# Join header and Payload with "." to create a valid (unsigned) JWT
$JWT = $EncodedHeader + "." + $EncodedPayload

# Get the private key object of your certificate
$PrivateKey = $Certificate.PrivateKey

# Define RSA signature and hashing algorithm
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256

# Create a signature of the JWT
$Signature = [Convert]::ToBase64String($PrivateKey.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT),$HashAlgorithm,$RSAPadding)) -replace '\+','-' -replace '/','_' -replace '='

# Join the signature to the JWT with "."
$JWT = $JWT + "." + $Signature

# Create a hash with body parameters
$Body = @{
    #client_id = $AppId
    #client_id = $ClientAppId
    client_assertion = $JWT
    client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    scope = $Scope
    grant_type = "client_credentials"

}

$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

# Use the self-generated JWT as Authorization
$Header = @{
    Authorization = "Bearer $JWT"
}

# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = @{
    ContentType = 'application/x-www-form-urlencoded'
    Method = 'POST'
    Body = $Body
    Uri = $Url
    Headers = $Header
}
$Request = Invoke-RestMethod @PostSplat

# Create header
$Header = @{
    Authorization = "$($Request.token_type) $($Request.access_token)"
}


return $header
         
       
   }