- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe to Topic
- Printer Friendly Page
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
AADSTS65001/AADSTS90099 when trying connect to AzAccount (or any other service) using Secure Application Model
Evening everyone!
I've been working on powershell scripts to help my company's operations department manage our CSP clients. I've set up the Secure Application Model using the following as guides:
(@sansbacher by the way- thank you for the excellent explanation)
The issue that I'm running into is that I cannot use the established SPN to log in to client environments, nor can I grant consent using DPA.
- Labels:
-
CSP
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
@JanoschUlmer @bycar @sansbacher thanks a lot for catching this!
This reply was automatically flagged as spam by the AI most likely due the larger number of screenshots, nevertheless it should not happen again 🙂
Thanks,
Andra
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
Well- I got AAD to successfully connect. Turns out my GenerateAuthToken function wasn't disconnecting from our CSP AZ account. Once I put that in I was able to connect to AAD.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
Hi @bycar - thanks, and you're welcome. Glad someone found all that useful! 😀
Can you post how far you ARE getting? Have any services been successful? (AzureAD, Msol/MS Online, or Exchange Online in particular) Can you post the errors you're getting, and at which steps/what pieces of code? Can you post your code, even test cost? (that either works, or minimally should but gives the errors)
According to: https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes
It sounds like YOU need to do the consent process. Did you run through the script Kelvin has on his CyberDrain site? [Or use the slightly edited version I posted in the thread you linked - look for my post on 01-09-2020 11:12 AM - with an attachment, use that script]. The errors imply consent hasn't been given. When you run that script (either one) you should have 2 pop-ups asking for Consent.
I believe the first needs to be an account with Global Admin privileges in YOUR (CSP) tenant. Or you need to go into Azure AD, App Registrations, find your app (whatever you provided for the -DisplayName for the script, eg: "Partner Center Web App") and go to Configured Permissions, and there should be green checks for the things it needs access to. If not you can click "Grant Admin consent" at the top. See Gavsto's post in that same thread from 01-08-2020 04:59 PM which has an attached .PNG file showing where his wasn't Consented. He clicked Grant Admin Consent" and it started working.
The second consent from script is for Exchange Online PowerShell, that needs to be an account that has "Admin agent" role within the Partner Center - the same permission needed to "Administer on behalf of a customer" - their Exch Online, or AAD, or SPO, or whatever. You need to note the username/UPN/email-address of the account you used as you'll need it in your scripts (along with the App ID, App Secret, RefreshTokens, etc) - in the example code I posted that you linked to it's in the $upn variable.
When I first performed the process I did it using an account that was O365/Azure AD Global Admin and Partner Center Admin Agent. I've since removed Global Admin from that account and everything still works (since it was just to perform consent for the Azure AD App registration to have various Directory.Read rights within our CSP Tenant). So you could try using an account that has both and see if that helps.
I'll also note: there's NO point where you need to have the CLIENTS/CUSTOMERS consent using Delegated Admin Permissions (DAP). The Clients/Customers will have already done that when they were added to your CSP Partner Portal - there's a link they click on, which grants your rights to see their Tenant and administer their Services using DAP. If you can already log into the CSP Partner Portal (with an "Admin agent" account) and connect to their AAD or ExO then they have already consented (to give you DAP) already.
So I'd start with: log into AAD and check that you've granted Consent to the App you created, and ensure the account you use for the $upn is an "Admin agent", if needed re-run the final ExO line in the script, which boils down to:
$Exchangetoken = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716' -Scopes 'https://outlook.office365.com/.default' -Tenant 'your-tenantid' -UseDeviceAuthentication
That should give you the Exchange Online PowerShell Refresh Token. That ApplicationId the same as -Module ExchangeOnline.
(assuming you already have the main Refresh Token for the App - if not, delete the app and just start again).
I hope that helps!
--Saul
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
OK! Now I guess I've got that all worked out and my only issue is connecting to AZAccount with the same SPN. Any wisdom there?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
@bycar I do not get why in the Connect-ClientAZ Account you use the Authorization code flow for getting the token for the customer, this should not be required at all, no need to do this interactive step.
So my approach in the sample scripts is to use the authorization flow only once for the initial token creation.
And everything else uses the same SPN... The below approach is certainly not a nice design, but showing the "proof" reg. the steps that work flawless for me
§SAMApp is the multi-tenant app of course)
$token = New-PartnerAccessToken -ApplicationId $SAMAppId -Scopes 'https://api.partnercenter.microsoft.com/user_impersonation' -ServicePrincipal -Credential $SAMcred -Tenant $PartnerTenant -UseAuthorizationCode
The storing the refresh token (Using another App/service principal to access the key vault in a different tenant - long story....)
Connect-AzAccount -ServicePrincipal -Credential $Keyvaultcred -Tenant $keyvaulttenant -Subscription $keyvaultsubscription
$RefreshTokenValue = ConvertTo-SecureString $token.refreshtoken -AsPlainText -Force
$RefreshTokenSecret = Set-AzKeyVaultSecret -VaultName $keyvaultname -Name $RefreshTokenSecretName -SecretValue $RefreshTokenValue
Then fetching the the token (using those steps and the long variable names to explain this in Demos)
Connect-AzAccount -ServicePrincipal -Credential $Keyvaultcred -Tenant $keyvaulttenant -Subscription $keyvaultsubscription
$RetrievedRefreshToken = (Get-AzKeyVaultSecret -vaultName $keyvaultname -name $RefreshTokenSecretName).SecretValueText
$EXRetrievedRefreshToken = (Get-AzKeyVaultSecret -vaultName $keyvaultname -name $ExchangeRefreshTokenSecretName).SecretValueText
generating new set of tokens from the old one
$newtoken = New-PartnerAccessToken -RefreshToken $RetrievedRefreshToken -Scopes 'https://api.partnercenter.microsoft.com/user_impersonation' -Credential $SAMcred -ServicePrincipal -Tenant $PartnerTenant
And then simply using this to generate tokens for the customer tenant ($customertenantDAP is a customer tenant where delegated admin privileges are set - does not work without this) - note that I'm still using the same SPN/App here:
$AzureToken = New-PartnerAccessToken -ApplicationId $SAMAppId -Credential $SAMcred -RefreshToken $newtoken.refreshToken -Scopes 'https://management.azure.com//user_impersonation' -ServicePrincipal -Tenant $CustomertenantDAP
$ADGraphToken = New-PartnerAccessToken -ApplicationId $SAMAppId -Credential $SAMcred -RefreshToken $newtoken.refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $CustomertenantDAP
Then clearing context for AZ before attempting to connect since otherwise bad things will happen 🙂
Clear-AzContext -Scope CurrentUser -Force
Connect-AzAccount -AccessToken $Azuretoken.AccessToken -AccountId $adminagent -GraphAccessToken $ADgraphToken.AccessToken -TenantId $CustomertenantDAP
... so this works for me.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
FYI- I answered all of your questions but my reply got flagged for moderation for some reason.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
Did anyone ever approve the reply, I didn't see it or get any response. Were you successful? If not, can you re-post?
--Saul
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
@Andra : Can you check why the reply was flagged?
- Mark as New
- Bookmark
- Subscribe
- Mute
- Permalink
- Report Inappropriate Content
Hi Saul-
I've broken the process down into a bunch of functions to allow for code reuse by other employees. Suffice to say that the process works entirely as expected against our CSP tenant, including connecting to our shared services tenant to access the AKV we're using to store secrets.
All of this code more or less works, but I've substituted any hardcoded IDs and stuff like vault names/secret names.
Client login (specifically AZ account, but AAD fails as well in the same ways):
function Connect-ClientAZAccount {
param (
[Parameter(Mandatory)][string]$CID
)
$tokenObject = GenerateTokenAuth
$refreshToken = UnsecureString($tokenObject.RefreshToken)
try {
$azureToken = New-PartnerAccessToken -ApplicationId $tokenObject.ApplicationId -Credential $tokenObject.credential -RefreshToken $refreshToken -Scopes 'https://management.azure.com/user_impersonation' -ServicePrincipal -Tenant $CID
$graphToken = New-PartnerAccessToken -ApplicationId $tokenObject.ApplicationId -Credential $tokenObject.credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $CID
}
catch {
$azureToken = New-PartnerAccessToken -ApplicationId $tokenObject.ApplicationId -Credential $tokenObject.credential -RefreshToken $refreshToken -Scopes 'https://api.partnercenter.microsoft.com/user_impersonation' -ServicePrincipal -Tenant $CID -UseAuthorizationCode
#$graphToken = New-PartnerAccessToken -ApplicationId $tokenObject.ApplicationId -Credential $tokenObject.credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $CID -UseAuthorizationCode
}
$ClientAZAccount = Connect-AzAccount -AccessToken $azureToken.AccessToken -AccountId $upn -GraphAccessToken $graphToken.AccessToken -TenantId $CID
return $ClientAZAccount
}
GenerateTokenAuth (Grabs an authentication info and returns it as an object)
function GenerateTokenAuth {
$azc = Get-AzContext -ListAvailable | Where-Object Tenant -match '00000000-0000-0000-0000-000000000000' | Select-AzContext
if (!$azc) {
Connect-AzAccount -Tenant '00000000-0000-0000-0000-000000000000' -ContextName "CSP"
} else {
Select-AzSubscription -TenantId "00000000-0000-0000-0000-000000000000" | Out-Null
}
$AppCred = New-Object System.Management.Automation.PSCredential([Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR(((Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSPAppId').SecretValue))), (Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSP-AppSecret').SecretValue)
$ThisObject = @{}
$ThisObject.Add('CSPTenant', '00000000-0000-0000-0000-000000000000')
$ThisObject.Add("UPN", $upn)
$ThisObject.Add("ApplicationId", (UnsecureString((Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSPAppId').SecretValue)))
$ThisObject.Add("RefreshToken", (RefreshToken))
$ThisObject.Add("Credential", $AppCred)
return $ThisObject
}
RefreshToken (Grabs+returns refresh token, and if expiration date < 14 days away it refreshes it + stores it)
function RefreshToken {
$azc = Get-AzContext -ListAvailable | Where-Object Tenant -match '00000000-0000-0000-0000-000000000000' | Select-AzContext
if (!$azc) {
Connect-AzAccount -Tenant '00000000-0000-0000-0000-000000000000' -ContextName "CSP"
} else {
Select-AzSubscription -TenantId '00000000-0000-0000-0000-000000000000' | Out-Null
}
$CSPR = Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSPR'
if ($cspr.Expires -le (get-date).adddays(14)) {
$AppSecret = Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSP-AppSecret'
$SecureAppID = (Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSPAppId').SecretValue
$AppId = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureAppID))
$credential = new-object -typename System.Management.Automation.PSCredential -argumentlist $AppID, $AppSecret.SecretValue
$token = New-PartnerAccessToken -ApplicationId $AppID -Scopes 'https://api.partnercenter.microsoft.com/user_impersonation' -ServicePrincipal -Credential $credential -Tenant '00000000-0000-0000-0000-000000000000' -UseAuthorizationCode
$refreshToken = convertto-securestring $token.RefreshToken -asplaintext -force
Set-AzKeyVaultSecret $CSPR -Expires (get-date).AddDays(90) -SecretValue $refreshToken
}
$refreshToken = (Get-AzKeyVaultSecret -VaultName "vault" -Name 'CSPR').SecretValue
return $refreshToken
}
UnsecureString (because I got tired of seeing the code to decrypt secure strings everywhere)
function UnsecureString {
param (
[Parameter(Mandatory)][securestring]$SecureString
)
$UString = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString))
return $UString
}
Running Connect-ClientAZAccount starts the expected authentication flow... browser pop up, MFA auth push then this:
Where the first red box is our app ID and the second red box is a client Tenant ID.
The app in our CSP tenant has the appropriate permissions and has been granted admin:
