If you are continually being prompted for authentication, when trying to use Azure portal, you problem with your primary refresh token (PRT) or more specifically, the PRT is not in a consistent state.
What is a Primary refresh token (PRT)
Well a primary refresh token (PRT) is a key security artifact used in Azure AD authentication that enables single sign-on (SSO) across applications and services in the Microsoft ecosystem. Let me explain its main purposes:
The PRT serves several critical functions:
- Device Authentication - The PRT proves that the device has been registered or joined to Azure AD and is in a trusted state. This is essential for conditional access and device-based security policies.
- Single Sign-On - Once a user authenticates successfully, the PRT allows them to access other Microsoft services without re-entering credentials. This creates a seamless experience across Microsoft 365, Azure resources, and other integrated applications.
- Security State Validation - The PRT contains claims about the device's security status, including:
- Whether the device is compliant with organizational policies
- If the device has passed recent health attestation
- The strength of the authentication method used
- Token Refresh - As its name suggests, the PRT can be used to obtain new access tokens for various resources without requiring user re-authentication. This happens automatically in the background.
The PRT is protected by hardware security features when possible (like TPM for Windows) and is only issued to managed devices that are either:
- Azure AD joined
- Hybrid Azure AD joined
- Azure AD registered
PRT Process Flow Diagram
I sometimes find it can be more helpful to look at the diagram and read a large block of text, so don’t know you will find a PRT slow diagram from what I understand:
<prt-diagram>
Local user diagnosis
If you wish to die, was this problem from the users point of View you can simply run the following command from Powershell:
dsregcmd /status
When this command is run you will get an output for the registration and user capabilities as detected by the server or device you are using, The section we are looking for here with SSO this example shows that no SSO is valid for the user:
This shows that SSO is trying to use a certificate for my SSO state and as you can see this certificate has failed to validate to our organisation:
A normal entry for valid SSO state will look like this:
xxxx
A normal entry for valid SSO state will look like this:
xxxx
Advice : If you would like to get commands to diagnose this from a "quick" diagnostic website you can use the link here
If you wish too do an analysis on your settings and PRT token on your local session then the script below can be run as a user locally on your device/desktop.
Script : AzurePRTTokenDiag.ps1
# Azure AD Authentication Diagnostic Script
# Non-invasive diagnostics for authentication issues
# Create output folder if it doesn't exist
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$outputPath = "AuthDiagnostics-$timestamp"
New-Item -ItemType Directory -Force -Path $outputPath
Write-Host "Starting authentication diagnostics..."
# Function to check device registration status
function Get-DeviceRegStatus {
Write-Host "Getting status information..."
$dsregcmd = dsregcmd /status
$dsregcmd | Out-File "$outputPath\dsregcmd_status.txt"
Write-Host "Getting detailed debug information..."
$dsregcmdDebug = dsregcmd /status /debug
$dsregcmdDebug | Out-File "$outputPath\dsregcmd_debug.txt"
# Also output directly to console for immediate viewing
Write-Host "`nDSRegCmd Status Output:"
$dsregcmd
Write-Host "`nDSRegCmd Debug Output:"
$dsregcmdDebug
# Parse key information
$azureAdJoined = $dsregcmd | Select-String "AzureAdJoined : YES" -Quiet
$prtValid = $dsregcmd | Select-String "AzureAdPrt : YES" -Quiet
Write-Host "Azure AD Joined: $azureAdJoined"
Write-Host "PRT Valid: $prtValid"
}
# Function to check event logs with provider discovery
function Get-AuthenticationEvents {
Write-Host "Checking authentication-related events..."
# Define specific logs and providers we want to check
$eventSources = @(
@{LogName = 'Application'; ProviderName = 'Microsoft-Windows-AAD/Operational'},
@{LogName = 'Application'; ProviderName = 'Microsoft AAD Authentication Provider'},
@{LogName = 'Application'; ProviderName = 'Microsoft AAD Authentication Library'},
@{LogName = 'System'; ProviderName = 'Microsoft-Windows-Authentication'},
@{LogName = 'Microsoft-Windows-AAD/Operational'}
)
foreach ($source in $eventSources) {
Write-Host "`nChecking $($source.LogName) - $($source.ProviderName)"
try {
$filterHash = @{
StartTime = (Get-Date).AddHours(-24)
}
if ($source.LogName) { $filterHash.LogName = $source.LogName }
if ($source.ProviderName) { $filterHash.ProviderName = $source.ProviderName }
$events = Get-WinEvent -FilterHashtable $filterHash -ErrorAction Stop |
Select-Object TimeCreated, Id, LevelDisplayName, Message
if ($events) {
$fileName = ($source.ProviderName -replace '[\\\/\:\*\?\"\<\>\|]', '_') + "_events.csv"
$events | Export-Csv "$outputPath\$fileName" -NoTypeInformation
Write-Host "Found $($events.Count) events"
# Display any error or warning events
$errorEvents = $events | Where-Object { $_.LevelDisplayName -match 'Error|Warning' } | Select-Object -First 5
if ($errorEvents) {
Write-Host "`nRecent Errors/Warnings:"
foreach ($event in $errorEvents) {
Write-Host "[$($event.TimeCreated)] $($event.LevelDisplayName): $($event.Message)"
}
}
} else {
Write-Host "No events found"
}
} catch {
if ($_.Exception.Message -match 'No events were found') {
Write-Host "No events found for this source"
} else {
Write-Host "Error accessing events: $_"
}
}
}
# Also check Application log specifically
Write-Host "`nChecking Application log for auth-related events..."
try {
$appLogEvents = Get-WinEvent -FilterHashtable @{
LogName = 'Application'
StartTime = (Get-Date).AddHours(-24)
} -ErrorAction Stop | Where-Object {
$_.ProviderName -like "*AAD*" -or
$_.ProviderName -like "*Auth*" -or
$_.ProviderName -like "*Azure*"
}
if ($appLogEvents) {
$appLogEvents | Select-Object TimeCreated, Id, LevelDisplayName, Message |
Export-Csv "$outputPath\application_auth_events.csv" -NoTypeInformation
Write-Host "Found $($appLogEvents.Count) auth-related events in Application log"
} else {
Write-Host "No auth-related events found in Application log"
}
} catch {
Write-Host "Error accessing Application log: $_"
}
}
# Function to check certificates
function Get-AuthCertificates {
Write-Host "Checking authentication certificates..."
try {
$allCerts = @()
# Check user certificates with expanded patterns
Write-Host "Checking CurrentUser certificates..."
$userCerts = Get-ChildItem -Path "Cert:\CurrentUser\My" -ErrorAction Stop | Where-Object {
$_.Subject -like "*CN=MS-Organization-Access*" -or
$_.Subject -like "*CN=Microsoft-Azure-AD-Access*" -or
$_.Subject -like "*CN=Microsoft-Azure-Device-Access*" -or
$_.Subject -like "*CN=Device Registration Access*" -or
$_.Issuer -like "*Windows Azure AD*" -or
$_.Issuer -like "*MS-Organization-Access*" -or
# Additional checks for PRT and SSO certificates
$_.EnhancedKeyUsageList.FriendlyName -contains "Client Authentication" -or
$_.EnhancedKeyUsageList.ObjectId -contains "1.3.6.1.5.5.7.3.2"
}
if ($userCerts) {
Write-Host "Found $($userCerts.Count) user certificates"
foreach ($cert in $userCerts) {
$certInfo = [PSCustomObject]@{
Store = 'CurrentUser'
Subject = $cert.Subject
Issuer = $cert.Issuer
NotBefore = $cert.NotBefore
NotAfter = $cert.NotAfter
Thumbprint = $cert.Thumbprint
HasPrivateKey = $cert.HasPrivateKey
EnhancedKeyUsage = ($cert.EnhancedKeyUsageList | ForEach-Object { $_.FriendlyName }) -join '; '
SerialNumber = $cert.SerialNumber
Template = ($cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'Certificate Template Name' } | Select-Object -ExpandProperty Format) -join '; '
}
$allCerts += $certInfo
}
# Display certificate details immediately
Write-Host "`nUser Certificate Details:"
foreach ($cert in $userCerts) {
Write-Host "`nSubject: $($cert.Subject)"
Write-Host "Issuer: $($cert.Issuer)"
Write-Host "Valid From: $($cert.NotBefore)"
Write-Host "Valid To: $($cert.NotAfter)"
Write-Host "Has Private Key: $($cert.HasPrivateKey)"
Write-Host "Enhanced Key Usage: $(($cert.EnhancedKeyUsageList | ForEach-Object { $_.FriendlyName }) -join ', ')"
Write-Host "Template: $(($cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'Certificate Template Name' } | Select-Object -ExpandProperty Format) -join ', ')"
if ($cert.NotAfter -lt (Get-Date)) {
Write-Host "WARNING: This certificate has EXPIRED!" -ForegroundColor Red
} elseif ($cert.NotAfter -lt (Get-Date).AddDays(30)) {
Write-Host "WARNING: This certificate will expire soon!" -ForegroundColor Yellow
}
}
} else {
Write-Host "No matching user certificates found"
}
# Check computer certificates
Write-Host "`nChecking LocalMachine certificates..."
$machineCerts = Get-ChildItem -Path "Cert:\LocalMachine\My" -ErrorAction Stop | Where-Object {
$_.Subject -like "*CN=MS-Organization-Access*" -or
$_.Subject -like "*CN=Microsoft-Azure-AD-Access*" -or
$_.Subject -like "*CN=Microsoft-Azure-Device-Access*" -or
$_.Issuer -like "*Windows Azure AD*"
}
if ($machineCerts) {
Write-Host "Found $($machineCerts.Count) machine certificates"
foreach ($cert in $machineCerts) {
$certInfo = [PSCustomObject]@{
Store = 'LocalMachine'
Subject = $cert.Subject
Issuer = $cert.Issuer
NotBefore = $cert.NotBefore
NotAfter = $cert.NotAfter
Thumbprint = $cert.Thumbprint
HasPrivateKey = $cert.HasPrivateKey
EnhancedKeyUsage = ($cert.EnhancedKeyUsageList | ForEach-Object { $_.FriendlyName }) -join '; '
SerialNumber = $cert.SerialNumber
Template = ($cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'Certificate Template Name' } | Select-Object -ExpandProperty Format) -join '; '
}
$allCerts += $certInfo
}
} else {
Write-Host "No matching machine certificates found"
}
if ($allCerts.Count -gt 0) {
$allCerts | Export-Csv "$outputPath\auth_certificates.csv" -NoTypeInformation
Write-Host "`nExported $($allCerts.Count) certificates to auth_certificates.csv"
} else {
Write-Host "`nNo Azure AD related certificates found in either store"
}
} catch {
Write-Host "Error accessing certificates: $_"
Write-Host "This could indicate permission issues or certificate store problems"
}
}
# Function to check network connectivity
function Test-AzureADConnectivity {
Write-Host "Testing Azure AD connectivity..."
$endpoints = @(
"login.microsoftonline.com",
"device.login.microsoftonline.com",
"enterpriseregistration.windows.net"
)
$results = foreach ($endpoint in $endpoints) {
$test = Test-NetConnection -ComputerName $endpoint -Port 443 -WarningAction SilentlyContinue
[PSCustomObject]@{
Endpoint = $endpoint
TcpTestSucceeded = $test.TcpTestSucceeded
PingSucceeded = $test.PingSucceeded
NameResolution = $test.NameResolutionSucceeded
}
}
$results | Export-Csv "$outputPath\connectivity_test.csv" -NoTypeInformation
}
# Function to check browser processes and profiles
function Get-BrowserInfo {
Write-Host "Checking browser processes..."
Get-Process | Where-Object {$_.ProcessName -match 'edge|chrome|firefox'} |
Select-Object ProcessName, StartTime, CPU, WorkingSet |
Export-Csv "$outputPath\browser_processes.csv" -NoTypeInformation
}
# Function to handle remediation with password check
function Start-Remediation {
Write-Verbose "Preparing for remediation..."
# Start remediation logging
$remediationLog = "$outputPath\remediation_log.txt"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
"Remediation Started: $timestamp`nUser: $env:USERNAME`n" | Out-File $remediationLog
# Simple password check
$password = Read-Host "Enter the password to proceed with remediation"
if ($password -ne "Skeletor") {
$message = "Incorrect password provided. Remediation cancelled."
Write-Verbose $message
Add-Content $remediationLog $message
return
}
Write-Host "Password accepted. Starting remediation..."
Add-Content $remediationLog "Password validated successfully. Beginning remediation actions..."
try {
# Clear browser data
Add-Content $remediationLog "`nAttempting to clear browser data..."
Clear-UserBrowserData
# Clear tokens
Add-Content $remediationLog "`nAttempting to clear user tokens..."
Clear-UserTokens
# Reset Azure registration
Add-Content $remediationLog "`nAttempting to reset Azure AD registration..."
Reset-UserAzureRegistration
Add-Content $remediationLog "`nRemediation actions completed successfully."
}
catch {
$errorMessage = "Error during remediation: $_"
Write-Host $errorMessage
Add-Content $remediationLog "`nERROR: $errorMessage"
}
finally {
$endTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Add-Content $remediationLog "`nRemediation Ended: $endTime"
}
}
# User-context safe remediation functions with logging
function Clear-UserBrowserData {
$message = "Clearing browser data for current user profile..."
Write-Host $message
Add-Content "$outputPath\remediation_log.txt" "`n$message"
# Implementation for clearing current user's browser profile
# Add specific actions here
Add-Content "$outputPath\remediation_log.txt" "Browser data clearing completed"
}
function Clear-UserTokens {
$message = "Clearing tokens for current user..."
Write-Host $message
Add-Content "$outputPath\remediation_log.txt" "`n$message"
# Implementation for clearing current user's tokens
# Add specific actions here
Add-Content "$outputPath\remediation_log.txt" "Token clearing completed"
}
function Reset-UserAzureRegistration {
$message = "Resetting Azure AD registration for current user..."
Write-Host $message
Add-Content "$outputPath\remediation_log.txt" "`n$message"
# Implementation for resetting current user's registration
# Add specific actions here
Add-Content "$outputPath\remediation_log.txt" "Azure AD registration reset completed"
}
# Main execution block
try {
# Run diagnostics
Get-DeviceRegStatus
Get-AuthenticationEvents
Test-AzureADConnectivity
Get-AuthCertificates
Get-BrowserInfo
Write-Host "`nDiagnostics completed successfully!"
Write-Host "Results saved to: $outputPath"
# Ask if user wants to attempt remediation
$answer = Read-Host "`nWould you like to attempt remediation of your profile? (Y/N)"
if ($answer -eq 'Y') {
Start-Remediation
}
else {
Write-Host "Remediation skipped. Please review the diagnostic logs."
}
}
catch {
Write-Host "Error during execution: $_"
}
finally {
# Generate summary report with problems detected
$problemsDetected = [System.Collections.ArrayList]@()
# Check DSReg status
$dsregStatus = Get-Content "$outputPath\dsregcmd_status.txt" -ErrorAction SilentlyContinue
if ($dsregStatus) {
if ($dsregStatus | Select-String "AzureAdJoined : NO") {
$problemsDetected.Add("Device is not Azure AD joined") | Out-Null
}
if ($dsregStatus | Select-String "AzureAdPrt : NO") {
$problemsDetected.Add("Primary Refresh Token is not present") | Out-Null
}
if ($dsregStatus | Select-String "TenantId :" -Context 0,1 | Select-String "^$") {
$problemsDetected.Add("TenantId is empty - possible registration issue") | Out-Null
}
}
# Check certificates if file exists
if (Test-Path "$outputPath\auth_certificates.csv") {
$certs = Import-Csv "$outputPath\auth_certificates.csv" -ErrorAction SilentlyContinue
if ($certs) {
$expiredCerts = $certs | Where-Object {
try {
[DateTime]::Parse($_.NotAfter) -lt (Get-Date)
} catch {
$false
}
}
$expiringCerts = $certs | Where-Object {
try {
$notAfter = [DateTime]::Parse($_.NotAfter)
$notAfter -gt (Get-Date) -and $notAfter -lt (Get-Date).AddDays(30)
} catch {
$false
}
}
if ($expiredCerts) {
$problemsDetected.Add("Found $($expiredCerts.Count) expired certificate(s)") | Out-Null
}
if ($expiringCerts) {
$problemsDetected.Add("Found $($expiringCerts.Count) certificate(s) expiring within 30 days") | Out-Null
}
}
} else {
$problemsDetected.Add("No Azure AD authentication certificates found") | Out-Null
}
# Check connectivity if file exists
if (Test-Path "$outputPath\connectivity_test.csv") {
$connTests = Import-Csv "$outputPath\connectivity_test.csv" -ErrorAction SilentlyContinue
if ($connTests) {
$failedTests = $connTests | Where-Object { -not $_.TcpTestSucceeded }
if ($failedTests) {
foreach ($test in $failedTests) {
$problemsDetected.Add("Connection failed to $($test.Endpoint)") | Out-Null
}
}
}
}
# Check for authentication errors in event logs
$aadEventFiles = Get-ChildItem -Path $outputPath -Filter "*AAD*events.csv" -ErrorAction SilentlyContinue
if ($aadEventFiles) {
$aadEvents = $aadEventFiles | ForEach-Object {
Import-Csv $_.FullName -ErrorAction SilentlyContinue
}
$recentErrors = $aadEvents | Where-Object {
try {
$_.LevelDisplayName -eq "Error" -and
[DateTime]::Parse($_.TimeCreated) -gt (Get-Date).AddHours(-24)
} catch {
$false
}
}
if ($recentErrors) {
$problemsDetected.Add("Found $($recentErrors.Count) authentication errors in the last 24 hours") | Out-Null
}
}
$summary = @"
Authentication Diagnostics Summary
--------------------------------
Generated: $(Get-Date)
Username: $env:USERNAME
Computer: $env:COMPUTERNAME
PROBLEMS DETECTED:
$($problemsDetected.Count) issue(s) found
$(if ($problemsDetected.Count -gt 0) {
$problemsDetected | ForEach-Object { "- $_`n" }
} else {
"No significant problems detected"
})
OUTPUT FILES:
The following files have been generated (if issues were found):
- dsregcmd_status.txt: Device registration status
- dsregcmd_debug.txt: Detailed device registration debug info
- *_events.csv: Various event log exports
- connectivity_test.csv: Network connectivity results
- auth_certificates.csv: Authentication certificates
- browser_processes.csv: Browser process information
Please provide these files to your IT support team for analysis.
"@
$summary | Out-File "$outputPath\summary.txt"
}
This will then export some diagnostic logs that you can manually go though to try to identify the problem as you can see below:
In the summary.txt you will find any issues that have been found as you can see below:
Authentication Diagnostics Summary
--------------------------------
Generated: 01/15/2025 15:43:47
Username: Lee.Croucher
Computer: BearWrk1
PROBLEMS DETECTED:
2 issue(s) found
- Device is not Azure AD joined
- Primary Refresh Token is not present