Skip to content

Instantly share code, notes, and snippets.

@dreadsend
Last active February 12, 2024 21:57
Show Gist options
  • Save dreadsend/fb46410db717ca3e937acbc9fccca754 to your computer and use it in GitHub Desktop.
Save dreadsend/fb46410db717ca3e937acbc9fccca754 to your computer and use it in GitHub Desktop.
PowerShell: Connecting to Microsoft Graph with a Users Username and Password instad of an interactive Flow
# Note: To use -publicClient you must explicitly enable public clients on the App Registration
# For details on why you should avoid this as much as Possible see https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth-ropc
function Connect-ROPCGraph {
param (
[Parameter(ParameterSetName = "PublicClient", Mandatory = $true)]
[Parameter(ParameterSetName = "ClientCert", Mandatory = $true)]
[Parameter(ParameterSetName = "ClientCredentials", Mandatory = $true)]
[ValidateNotNull()]
[System.Management.Automation.PSCredential]$userCredentials,
[Parameter(ParameterSetName = "PublicClient", Mandatory = $true)]
[switch]$publicClient,
[Parameter(ParameterSetName = "PublicClient", Mandatory = $false)]
[Parameter(ParameterSetName = "ClientCert", Mandatory = $true)]
[Parameter(ParameterSetName = "ClientCredentials", Mandatory = $true)]
[string]$tenantId,
[Parameter(ParameterSetName = "PublicClient", Mandatory = $true)]
[Parameter(ParameterSetName = "ClientCert", Mandatory = $true)]
[Parameter(ParameterSetName = "ClientCredentials", Mandatory = $true)]
[string]$clientId,
[Parameter(ParameterSetName = "PublicClient", Mandatory = $false)]
[Parameter(ParameterSetName = "ClientCert", Mandatory = $false)]
[Parameter(ParameterSetName = "ClientCredentials", Mandatory = $false)]
[array]$scopes=@(".default"),
[Parameter(ParameterSetName = "ClientCert", Mandatory = $true)]
[ValidateNotNull()]
[string]$certificateThumbprint,
[Parameter(ParameterSetName = "ClientCredentials", Mandatory = $true)]
[ValidateNotNull()]
[securestring]$clientSecret
)
# Depending on which Type of Client Credentials were used we generate the Request Body
switch ($PSCmdlet.ParameterSetName) {
"PublicClient" {
$Body = @{
client_id = $clientId
scope = [string]$scopes
username = $userCredentials.UserName
password = $userCredentials.GetNetworkCredential().Password
grant_type = "password"
}
}
"ClientCert" {
# If we are using Certificate Credentials we have to generate a JWT Assertion
# Based on https://adamtheautomator.com/powershell-graph-api/ - the original certificate usage did not work for me though
try {
# Load Certificate
$Certificate = Get-Item "Cert:\CurrentUser\My\$certificateThumbprint" -ErrorAction Stop
# Get base64 hash of certificate in Web Encoding
$CertificateBase64Hash = [System.Convert]::ToBase64String($Certificate.GetCertHash()) -replace '\+', '-' -replace '/', '_' -replace '='
}
catch {
throw "Error Reading Certificate"
}
$StartDate = (Get-Date "1970-01-01T00:00:00Z").ToUniversalTime()
# Create JWT timestamp for expiration
$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"
x5t = $CertificateBase64Hash
}
# Create JWT payload
$JWTPayLoad = @{
aud = "https://login.microsoftonline.com/$tenantID/oauth2/token"
exp = $JWTExpiration
iss = $clientID
jti = [guid]::NewGuid()
nbf = $NotBefore
sub = $clientID
}
# 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)
$JWT = $EncodedHeader + "." + $EncodedPayload
# Define RSA signature and hashing algorithm
$RSAPadding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::SHA256
# Sign the JWT
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)
$Signature = [Convert]::ToBase64String(
$rsaCert.SignData([System.Text.Encoding]::UTF8.GetBytes($JWT), $HashAlgorithm, $RSAPadding)
) -replace '\+', '-' -replace '/', '_' -replace '='
# Add Signature to JWT
$JWT = $JWT + "." + $Signature
$Body = @{
client_id = $clientId
client_assertion = $JWT
client_assertion_type = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
scope = [string]$scopes
username = $userCredentials.UserName
password = $userCredentials.GetNetworkCredential().Password
grant_type = "password"
}
}
"ClientCredentials" {
$Body = @{
client_id = $clientId
client_secret = [System.Net.NetworkCredential]::new("", $clientSecret).Password
scope = [string]$scopes
username = $userCredentials.UserName
password = $userCredentials.GetNetworkCredential().Password
grant_type = "password"
}
}
}
$params = @{
Uri = "https://login.microsoftonline.com/$tenantID/oauth2/v2.0/token"
Method = 'POST'
ContentType = 'application/x-www-form-urlencoded'
Body = $Body
# If we use a JWT we must add an Authorization Header
Headers = if ($JWT) { @{ Authorization = "Bearer $JWT" } }
}
$accessToken = ConvertTo-SecureString (Invoke-RestMethod @params -ErrorAction Stop).access_token -AsPlainText -Force
# Use our Access token to Connect to Microsoft Graph
Connect-MgGraph -AccessToken $accessToken -NoWelcome
# Clear Senstive Values
$sensitiveVars = @("userCredentials","accessToken","body","params","jwt","signature")
Remove-Variable $sensitiveVars
[gc]::collect()
}
@dreadsend
Copy link
Author

dreadsend commented Jan 28, 2024

I would like to stress here, that this should only be used in very very rare cases. Probably 99% of the time using Application Permissions and a Corresponding natively Supported Auth flow is the better way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment