We are using the following PowerShell script to monitor Azure AD authentication-enabled URLs in Splunk. However, when incorrect credentials are entered, a 200 response code is returned instead of the expected failure response (e.g., 401 Unauthorized).
Has anyone encountered this issue? Please help us rectify this and ensure that incorrect credentials are flagged with the appropriate response code.
# Prompt User for Credentials
$credential = Get-Credential
# Define Target URL
$targetUrl = "<TARGET_URL>" # URL to monitor
# Convert Credentials to Base64 for Authorization Header
$username = $credential.UserName
$password = $credential.GetNetworkCredential().Password
$authValue = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$username`:$password"))
$headers = @{ Authorization = "Basic $authValue" }
# Send Request with Authorization
try {
$response = Invoke-WebRequest -Uri $targetUrl -Headers $headers -Method Get -UseBasicParsing -ErrorAction Stop
# Check if the server actually challenges for authentication
if ($response.StatusCode -eq 200 -and $response.Headers["WWW-Authenticate"]) {
Write-Host "Authentication failed: Invalid credentials provided."
} else {
Write-Host "Response Code: $($response.StatusCode)"
}
} catch {
if ($_.Exception.Response.StatusCode -eq 401) {
Write-Host "Authentication failed: Invalid credentials provided."
} else {
Write-Host "Request failed with error: $($_.Exception.Message)"
}
}
Hi @Devika_20
I think Azure AD-protected URL expects (OAuth 2.0 / OpenID Connect) authentication rather than Basic auth, its worth reviewing the docs on these URLs.
Regarding the 200 status:
Unauthenticated requests to Azure AD-protected resources usually trigger a redirection (HTTP 302) to the Microsoft login page (login.microsoftonline.com).
Invoke-WebRequest might receive this 302 redirect. By default, it might try to follow it. However, the ultimate login page requires interactive user input (or specific OAuth flows), which your script isn't performing.
It's also possible that the initial response before the redirect, or the response at the redirect URL itself if redirects aren't followed correctly, is interpreted as a 200 OK by Invoke-WebRequest, especially if -UseBasicParsing simplifies how responses are handled. The server isn't explicitly rejecting the credentials with a 401 because it wasn't even trying to process them via Basic Auth; it was trying to initiate the standard Azure AD login flow.
The other thing to check is the logic around the 200 status and WWW-Authenticate headers, I believe the check if ($response.StatusCode -eq 200 -and $response.Headers["WWW-Authenticate"]) inside the try block is incorrect. A 200 OK response signifies success. The WWW-Authenticate header is typically sent with a 401 Unauthorized response to tell the client how to authenticate, not upon success. This condition would likely never be true in a standard scenario.
Ultimately correct way to achieve this would be using OAuth
You would need to modify your script to authenticate using an OAuth 2.0 flow appropriate for a non-interactive script. The Client Credentials Flow is the standard and most secure method for service-to-service or script-based authentication against Azure AD.
Steps:
Azure AD App Registration:
Modify the PowerShell Script: Replace the Basic Auth logic with OAuth 2.0 Client Credentials Flow.
The following template would be a good starting point:
# --- Configuration ---
$clientId = "YOUR_APP_REGISTRATION_CLIENT_ID"
$clientSecret = "YOUR_APP_REGISTRATION_CLIENT_SECRET" # Consider using Azure Key Vault or secure storage
$tenantId = "YOUR_AZURE_AD_TENANT_ID"
$targetUrl = "<TARGET_URL>" # URL to monitor
# Define the scope. Often 'https://resource.example.com/.default' or 'api://<api_client_id>/.default'
# For Microsoft Graph API it might be 'https://graph.microsoft.com/.default'
# Check the documentation for your specific targetUrl API or Azure service
$scope = "YOUR_API_SCOPE/.default" # e.g., "api://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/.default" or resource URI + "/.default"
# --- Get Access Token ---
$tokenRequestBody = @{
Grant_Type = "client_credentials"
Scope = $scope
Client_Id = $clientId
Client_Secret = $clientSecret
}
$tokenEndpoint = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
try {
Write-Verbose "Requesting Access Token from $tokenEndpoint"
$tokenResponse = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body $tokenRequestBody -ContentType 'application/x-www-form-urlencoded'
$accessToken = $tokenResponse.access_token
Write-Verbose "Successfully obtained Access Token."
} catch {
Write-Host "Error obtaining Access Token: $($_.Exception.Message)"
# Log detailed error for Splunk
Write-Host "OAuth Token Request Failed. Status Code: $($_.Exception.Response.StatusCode). Response Body: $($_.Exception.Response.GetResponseStream() | Foreach-Object { New-Object System.IO.StreamReader($_) } | Foreach-Object { $_.ReadToEnd() })"
exit 1 # Exit because we cannot proceed without a token
}
# --- Send Request with Bearer Token ---
$headers = @{ Authorization = "Bearer $accessToken" }
try {
Write-Verbose "Sending request to $targetUrl"
# -MaximumRedirection 0 prevents following redirects which might mask the initial auth status
$response = Invoke-WebRequest -Uri $targetUrl -Headers $headers -Method Get -UseBasicParsing -ErrorAction Stop -MaximumRedirection 0
# Successful request (usually 2xx)
# Log success for Splunk
Write-Host "Response Code: $($response.StatusCode)"
Write-Host "Monitoring check successful for $targetUrl"
} catch {
# Handle HTTP errors (like 401 Unauthorized, 403 Forbidden, etc.)
$statusCode = $_.Exception.Response.StatusCode
$statusDescription = $_.Exception.Response.StatusDescription
# Log failure for Splunk
Write-Host "Response Code: $statusCode"
Write-Host "Status Description: $statusDescription"
Write-Host "Monitoring check failed for $targetUrl. Error: $($_.Exception.Message)"
# You can add specific logging/alerting for critical codes like 401/403
if ($statusCode -eq [System.Net.HttpStatusCode]::Unauthorized -or $statusCode -eq [System.Net.HttpStatusCode]::Forbidden) {
Write-Host "CRITICAL: Authentication or Authorization failed ($statusCode)."
# Add specific Splunk logging for auth failure here
}
# Optional: Log response body for debugging, be careful with sensitive data
# $errorResponseBody = $_.Exception.Response.GetResponseStream() | Foreach-Object { New-Object System.IO.StreamReader($_) } | Foreach-Object { $_.ReadToEnd() }
# Write-Host "Error Response Body: $errorResponseBody"
}
Please let me know how you get on and consider adding karma to this or any other answer if it has helped.
Regards
Will