-
-
Notifications
You must be signed in to change notification settings - Fork 88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Grant flow Bad Request #72
Comments
Please try the setup script linked from the readme: #61 (comment). |
Hi, thanks! My config looks good now.
Im pretty sure I did the steps on AAD correctly. Do I have to set : |
It's hard to diagnose issues with the client credentials grant flow because the setup is so opaque. But I'd assume that here there is some AAD misconfiguration causing the authorisation failure. Note that this error is raised by the proxy when requesting credentials, rather than actually using them, so check you've properly set up and given admin authorisation for your AAD client. The |
Hi Simon, Its working now, Im not sure where the issue was but I wrote a Powershell script to configure all of this in AAD so I dont forget any steps on AAD in the future. Maybe the script is useful for someone else too. # Requires AzureADPreview Module;
# Install-Module AzureADPreview
# Install-Module -Name AzureRM
# User must be able to create apps and consent (Global Admin, ...)
Param (
$appName = "YourAppName",
$redirecturi = "http://localhost",
$fullaccessuser = ('test1@tld','test2@tld')
)
#Login to Azure Active Directory
$credential = Get-Credential
Connect-AzureAd -Credential $credential;
#MS Graph stuff:
#Microsoft Graph delegated permissions
$servicePrincipalNameGraph = "Microsoft Graph";
$servicePrincipalNameOauth2PermissionsGraph = @("IMAP.AccessAsUser.All", "POP.AccessAsUser.All", "SMTP.Send");
# Get MS Graph
$servicePrincipalGraph = Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq $servicePrincipalNameGraph };
#MS Graph permissions
$reqGraph = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess";
$reqGraph.ResourceAppId = $servicePrincipalGraph.AppId;
$servicePrincipalGraph.Oauth2Permissions | Where-Object { $_.Value -in $servicePrincipalNameOauth2PermissionsGraph} | ForEach-Object {
$permission = $_
$delPermission = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $permission.Id,"Scope" #delegate permission (oauth) are always "Scope"
$reqGraph.ResourceAccess += $delPermission
}
#EXO stuff:
#Office 365 Exchange Online app permissions
$servicePrincipalNameExchangeOnline = "Office 365 Exchange Online";
$servicePrincipalNameOauth2PermissionsExchangeOnline = @("IMAP.AccessAsApp", "POP.AccessAsApp");
#Get Office 365 Exchange Online
$servicePrincipalExchangeOnline = Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq $servicePrincipalNameExchangeOnline };
#Exchange Online permissions
$reqExchangeOnline = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess";
$reqExchangeOnline.ResourceAppId = $servicePrincipalExchangeOnline.AppId;
$servicePrincipalExchangeOnline.AppRoles | Where-Object { $_.Value -in $servicePrincipalNameOauth2PermissionsExchangeOnline} | ForEach-Object {
$permission = $_
$appPermission = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $permission.Id,"Role"
$reqExchangeOnline.ResourceAccess += $appPermission
}
#Define the Clientsecret here
$secretstartdate = Get-Date
$secretenddate = $secretstartdate.AddMonths(24)
$description = "OAuth_secret"
New-AzureADApplication -DisplayName $appName -AvailableToOtherTenants:$true -PublicClient:$false -ReplyUrls $redirecturi -RequiredResourceAccess $reqGraph, $reqExchangeOnline;
$newapp = Get-AzureADApplication -SearchString $appName;
$clientsecret = New-AzureADApplicationPasswordCredential -ObjectId $newapp.ObjectId -StartDate $secretstartdate -EndDate $secretenddate -CustomKeyIdentifier $description;
"`n"
"ClientId: " + $newapp.AppId;
"ClientSecret: " + $clientsecret.Value
"TenantId: " + (Get-AzureADTenantDetail).ObjectId;
"`n"
"You can check your AAD app here: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/" + $newapp.AppId + "/objectId/" + $newapp.ObjectId + "/isMSAApp/";
#Give it some time to appear in azuread so the next step will run successfull
Start-Sleep -Seconds 60
#Add you as owner
$AppOwner = get-azureaduser -all $true -Filter "UserPrincipalName eq '$($credential.UserName)'"
Add-AzureADApplicationOwner -ObjectId $($newapp.ObjectId) -RefObjectId $($AppOwner.ObjectId)
#Grant Admin consent
Login-AzureRmAccount -Credential $credential
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $TenantId, $null, "Never", $null, "74658136-14ec-4630-ad9b-26e160ff0fc6")
$headers = @{
'Authorization' = 'Bearer ' + $token.AccessToken
'X-Requested-With'= 'XMLHttpRequest'
'x-ms-client-request-id'= [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()}
$url = "https://main.iam.ad.ext.azure.com/api/RegisteredApplications/$($newapp.AppId)/Consent?onBehalfOfAll=true"
Invoke-RestMethod -Uri $url -Headers $headers -Method POST -ErrorAction Stop
#Register Service principal
Connect-ExchangeOnline -Credential $credential
$serviceprincipalexo = New-ServicePrincipal -AppId $newapp.AppId -ServiceId $newapp.ObjectId -Displayname $newapp.DisplayName -Organization $tenantId
#Grant your application's service principal full access to the specified mailboxes.
ForEach ($fullaccessuser in $fullaccessusers) {
Add-MailboxPermission -Identity "$fullaccessusers" -User $serviceprincipalexo.Id -AccessRights FullAccess
} |
Thanks for the contribution! (I edited it slightly to make a code block rather than individual lines – you can use three backticks to do this if needed) |
`#EXO stuff: #Office 365 Exchange Online app permissions #Get Office 365 Exchange Online #Exchange Online permissions ... Are you purposefully configuring your registered application to support both 'Delegation' (Graph) and 'Flow' (Exchange) permissions? Based on this comment I believe the Graph permissions are not being used in the Flow mode: Thanks in advance for the clarification |
Is there a mistake in this script `Param ( #Grant your application's service principal full access to the specified mailboxes. should be `Param ( #Grant your application's service principal full access to the specified mailboxes. |
Instead of creating a specific list of users... and granting full permissions to all of them... would it work instead to add "full_access_as_ap" into your Exchange Online Permissions"? If so this would make it more maintainable too... |
Hey, Thanks for your input. I made a simple if statement to configure either this Grant Flow stuff or only the MS Graph permissions. As long as it works Im happy.
|
Hey, I'm pretty sure I've tried that and it did not work. :( |
You are correct - it will not work ... It only applies to EWS and not POP or IMAP |
Simple "if" works fine... just wanted to clarify what is required for which scenario... script is nice to automate and not use the UI. From here: https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access
Even though the link is about EWS - we should be able to make use of the idea for our needs by:
Can you add this to your script? (creating a mail enabled security group and assigning the principal permissions to it) |
In your script:
The redirectURL will only work where the proxy is deployed on a location where port 80 traffic is not already in use... (quite commonly used) Can you update the script so $redirectURI takes a DomainName and Port in the params ? e.g. http://domainName:Port) |
My understanding is that when creating/registering an application the redirectURI is optional... and only used to make sure traffic is restricted to that URI only.... but in general the proxy choose the redirectURL for the application... would it be better not to define it in the application at all? Does it work not to put one in the application and let the proxy dictate the redirectURL? |
Sure I can take a look at it, so it will accept DomainName and Port. The STunnel on the Client System is setting up a secure connection to our public FQDN "OAuth.domain.tld" and there is a SNAT in our Watchguard Firewall that will redirect it to the Proxy Server. Then I just have to send the User the "login.microsoft" URI and after he successful logged in my ".php.ini" has a simple script that will write the "https://oauth.domain.tld/?code=xyz" part into a CSV file on the Server so I just have to enter it into the proxy application. It's a really comfortable way to authenticate Usermailboxes in our scenario and of course we restricted the acces with proxy actions like pattern matches on our Firewall to make it as secure as we can. :) To make sure STunnel can set up a secure connection we use "winacme" on the proxy server to autorenewal the certificate .pem files the Proxy needs. |
Thanks for the detailed response... still coming up to speed on all of the OAuth flow nuances... would you mind creating a diagram for me so I can better understand? Here is a diagram I have created for the Client Flow configuration |
I've tested it, it works with DomainName and Port like that. Also I corrected the "fullaccesuser" mistake inside the foreach loop. # Requires AzureADPreview Module;
# Install-Module AzureADPreview
# Install-Module -Name AzureRM
# User must be able to create apps and consent (Global Admin, ...)
Param (
$appName = "TestApp",
$redirecturi = "http://localhost:8081",
$fullaccessuser = ('test1@tld','test2@tld'),
$grantflowrequirements = $false
)
#Login to Azure Active Directory
$credential = Get-Credential
Connect-AzureAd -Credential $credential;
#MS Graph stuff:
#Microsoft Graph delegated permissions
$servicePrincipalNameGraph = "Microsoft Graph";
$servicePrincipalNameOauth2PermissionsGraph = @("IMAP.AccessAsUser.All", "POP.AccessAsUser.All", "SMTP.Send");
# Get MS Graph
$servicePrincipalGraph = Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq $servicePrincipalNameGraph };
#MS Graph permissions
$reqGraph = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess";
$reqGraph.ResourceAppId = $servicePrincipalGraph.AppId;
$servicePrincipalGraph.Oauth2Permissions | Where-Object { $_.Value -in $servicePrincipalNameOauth2PermissionsGraph} | ForEach-Object {
$permission = $_
$delPermission = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $permission.Id,"Scope" #delegate permission (oauth) are always "Scope"
$reqGraph.ResourceAccess += $delPermission
}
If($grantflowrequirements -eq $true){
#EXO stuff:
#Office 365 Exchange Online app permissions
$servicePrincipalNameExchangeOnline = "Office 365 Exchange Online";
$servicePrincipalNameOauth2PermissionsExchangeOnline = @("IMAP.AccessAsApp", "POP.AccessAsApp");
#Get Office 365 Exchange Online
$servicePrincipalExchangeOnline = Get-AzureADServicePrincipal -All $true | Where-Object { $_.DisplayName -eq $servicePrincipalNameExchangeOnline };
#Exchange Online permissions
$reqExchangeOnline = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess";
$reqExchangeOnline.ResourceAppId = $servicePrincipalExchangeOnline.AppId;
$servicePrincipalExchangeOnline.AppRoles | Where-Object { $_.Value -in $servicePrincipalNameOauth2PermissionsExchangeOnline} | ForEach-Object {
$permission = $_
$appPermission = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" -ArgumentList $permission.Id,"Role"
$reqExchangeOnline.ResourceAccess += $appPermission
}
}
else{Write-Host "Grantflow requirements disabled"}
#Define the Clientsecret here
$secretstartdate = Get-Date
$secretenddate = $secretstartdate.AddMonths(24)
$description = "OAuth_secret"
If($grantflowrequirements -eq $true){
New-AzureADApplication -DisplayName $appName -AvailableToOtherTenants:$true -PublicClient:$false -ReplyUrls $redirecturi -RequiredResourceAccess $reqGraph, $reqExchangeOnline;
}
else
{New-AzureADApplication -DisplayName $appName -AvailableToOtherTenants:$true -PublicClient:$false -ReplyUrls $redirecturi -RequiredResourceAccess $reqGraph;}
$newapp = Get-AzureADApplication -SearchString $appName;
$clientsecret = New-AzureADApplicationPasswordCredential -ObjectId $newapp.ObjectId -StartDate $secretstartdate -EndDate $secretenddate -CustomKeyIdentifier $description;
"`n"
"ClientId: " + $newapp.AppId;
"ClientSecret: " + $clientsecret.Value
"TenantId: " + (Get-AzureADTenantDetail).ObjectId;
"`n"
"You can check your AAD app here: https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/" + $newapp.AppId + "/objectId/" + $newapp.ObjectId + "/isMSAApp/";
#Give it some time to appear in azuread so the next step will run successfull
Start-Sleep -Seconds 60
#Add you as owner
$AppOwner = get-azureaduser -all $true -Filter "UserPrincipalName eq '$($credential.UserName)'"
Add-AzureADApplicationOwner -ObjectId $($newapp.ObjectId) -RefObjectId $($AppOwner.ObjectId)
#Grant Admin consent
Login-AzureRmAccount -Credential $credential
$context = Get-AzureRmContext
$tenantId = $context.Tenant.Id
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $TenantId, $null, "Never", $null, "74658136-14ec-4630-ad9b-26e160ff0fc6")
$headers = @{
'Authorization' = 'Bearer ' + $token.AccessToken
'X-Requested-With'= 'XMLHttpRequest'
'x-ms-client-request-id'= [guid]::NewGuid()
'x-ms-correlation-id' = [guid]::NewGuid()}
$url = "https://main.iam.ad.ext.azure.com/api/RegisteredApplications/$($newapp.AppId)/Consent?onBehalfOfAll=true"
Invoke-RestMethod -Uri $url -Headers $headers -Method POST -ErrorAction Stop
If($grantflowrequirements -eq $true){
#Register Service principal
Connect-ExchangeOnline -Credential $credential
$serviceprincipalexo = New-ServicePrincipal -AppId $newapp.AppId -ServiceId $newapp.ObjectId -Displayname $newapp.DisplayName -Organization $tenantId
#Grant your application's service principal full access to the specified mailboxes.
ForEach ($fullaccessuser in $fullaccessusers) {
Add-MailboxPermission -Identity "$fullaccessuser" -User $serviceprincipalexo.Id -AccessRights FullAccess
}
}
else{Write-Host "Grantflow requirements disabled"} ``` |
Hi Simon,
I want to try the "grant flow" support. But Im stuck and not sure what Im doing wrong.
I did all of the required steps on AAD and the Powershell cmdlets described on:
https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth#use-client-credentials-grant-flow-to-authenticate-imap-and-pop-connections
My account setup in the emailproxy.config:
[testuser@domain.com]
permission_url =
token_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
oauth2_scope = https://outlook.office365.com/.default https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/POP.AccessAsUser.All https://outlook.office365.com/SMTP.Send offline_access
redirect_uri = http://localhost
client_id = xxxxxxxxxxxxxxxxxxx
client_secret = yyyyyyyyyyyyyyyy
Not sure if I have to add the ".default" URI into the oauth2_scope.
When I start the emailproxy.py --no-gui ,
telnet localhost 1995 and provide my testuser credentials following happens.
Caught exception while requesting OAuth 2.0 credentials for testuser@domain.com: <HTTPError 400: 'Bad Request'>
Any idea which step Im probably missing?
The text was updated successfully, but these errors were encountered: