Closed manuelmazzuola closed 4 years ago
@manuelmazzuola can you expand on your scenario? Would On-Behalf-Of flow work for you? If not, how does Admin-On-Behalf-Of differ?
The scenario should be, the CSP admin agent add a new customer on partner center, then add a new azure plan to the newly customer, and finally, the part that I cannot reproduce via API, the CSP admin agent has to create resources inside the azure customer subscription.
Spoiler: I already managed that, but using the ROPC flow, using username/password of the admin agent, but I cannot do that anymore because of MFA.
So, I need a way to get interactively a token from the customers, that authorize the admin agent to retrieve a specific token for the csp customer tenant to use on the scope management.azure.com//.default
.
@manuelmazzuola To make sure I understand, you're scenario is:
app -> CSP admin agent -> Partner Center API
Where you own the CSP admin agent, and you can get a token using Username/Password now, but because of MFA you have to move away from this flow. Instead you'd like the app to do Authorization Code flow, which passes the token to CSP admin agent, who then exchanges token with AAD to get a token to access Partner Center API on-behalf-of the app?
@sangonzal yes, except the last part, I'd like the app to do Authorization Code flow, which passes the token to CSP admin agent, who then exchanges token with AAD to get a token to access to the customer azure tenant (to provision resources, virtual machines, etc..)
As i said, I already do that, using the confidential client with username and password of the admin, but I have to move away from this flow.
@sangonzal look, the token acquired using the following code works:
PublicClientApplication app = PublicClientApplication.builder( PUBLIC_CLIENT_ID )
.authority( "https://login.microsoftonline.com/testpocazureeng.onmicrosoft.com" )
.build();
UserNamePasswordParameters parameters = UserNamePasswordParameters
.builder( Collections.singleton( "https://management.azure.com/.default" ), ADMIN_USERNAME, ADMIN_PASSWORD.toCharArray() )
.build();
IAuthenticationResult result = app.acquireToken( parameters ).join();
the following code not:
IClientCredential credential = ClientCredentialFactory.createFromSecret(MY_SECRET);
ConfidentialClientApplication cca =
ConfidentialClientApplication
.builder( CLIENT_ID, credential)
.authority("https://login.microsoftonline.com/testpocazureeng.onmicrosoft.com")
.build();
ClientCredentialParameters parameters =
ClientCredentialParameters
.builder(Collections.singleton( "https://management.azure.com/.default" ))
.build();
IAuthenticationResult tokenResult = cca.acquireToken( parameters ).join();
it says:
Exception in thread "main" com.microsoft.azure.CloudException: Status code 401, {"error":{"code":"InvalidAuthenticationTokenTenant","message":"The access token is from the wrong issuer 'https://sts.windows.net/96d7c757-e1a4-4ef3-88e3-4f3801f19a17/'. It must match the tenant 'https://sts.windows.net/baaf7b71-4cdf-4542-8d8b-ab2f05780b04/' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/baaf7b71-4cdf-4542-8d8b-ab2f05780b04' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later."}}: The access token is from the wrong issuer 'https://sts.windows.net/96d7c757-e1a4-4ef3-88e3-4f3801f19a17/'. It must match the tenant 'https://sts.windows.net/baaf7b71-4cdf-4542-8d8b-ab2f05780b04/' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/baaf7b71-4cdf-4542-8d8b-ab2f05780b04' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later.
testpocazureeng.onmicrosoft.com
is the tenant of the customer, I'm just trying to figure out an alternative method to the first one, but i can't :D
@manuelmazzuola is the tenant id for testpocazureeng.onmicrosoft.com
96d7c757-e1a4-4ef3-88e3-4f3801f19a17
or baaf7b71-4cdf-4542-8d8b-ab2f05780b04
?
The error message is saying that you acquired a token for 96d7c757-e1a4-4ef3-88e3-4f3801f19a17
, but are trying to access resources for baaf7b71-4cdf-4542-8d8b-ab2f05780b04
. What happens if you use authority in "https://login.microsoftonline.com/baaf7b71-4cdf-4542-8d8b-ab2f05780b04"
?
I don't think this will solve your MFA issue though, as client credentials is not an interactive flow. Take a look at https://github.com/Azure-Samples/ms-identity-java-webapi . In your scenario, the client app would be the msal-web-sample
and the CSP admin agent would be msal-obo-sample
.
~What happens if you use authority in "https://login.microsoftonline.com/baaf7b71-4cdf-4542-8d8b-ab2f05780b04"?~
EDIT
sorry I had pasted the wrong message error, the right one was:
The client 'a93b0898-a1b1-439c-aaf5-40257d928067' with object id 'a93b0898-a1b1-439c-aaf5-40257d928067' does not have authorization to perform action 'Microsoft.Network/register/action' over scope '/subscriptions/730c3e12-f5d9-401b-bb47-3b7497f310db' or the scope is invalid.
I don't think this will solve your MFA issue though, as client credentials is not an interactive flow. Take a look at https://github.com/Azure-Samples/ms-identity-java-webapi . In your scenario, the client app would be the msal-web-sample and the CSP admin agent would be msal-obo-sample.
I'll take a look, I let you know
@sangonzal nope, same issue, in the sample app I've changed the scope from graph to azure (and added the right permissions the the webapp and webapi apps) and I get the same permission error:
Exception in thread "main" org.jclouds.rest.AuthorizationException: {"error":{"code":"AuthorizationFailed","message":"The client 'admin@testpocazureeng.onmicrosoft.com' with object id '28cfc43b-2946-49aa-9cc0-f718a441d27e' does not have authorization to perform action 'Microsoft.Resources/subscriptions/providers/read' over scope '/subscriptions/730c3e12-f5d9-401b-bb47-3b7497f310db' or the scope is invalid. If access was recently granted, please refresh your credentials."}}
In the on-behalf-of flow used in the sample you linked to me, the admin acts on behalf of the customer, but he inherit also the non-permissions to manage azure resources.
I think that it's impossible without giving to the customer the proper permissions on the owned azure subscription.
Here (https://azure.microsoft.com/en-gb/offers/ms-azr-0145p/) says that:
When a new Microsoft Azure subscription is created in a customer tenant, the partner is granted
owner rights on this subscription; however, the customer is not granted owner rights by default.
Therefore, the customer cannot log in to the Microsoft Azure Management Portal and
create/start/stop new Azure services for the specific subscription – the partner has to do this on
behalf of the customer.
At this point after all the tests i did i think that the above statement applies only to the UI and not using the REST API.
This is the endpoint called when on the interface, as partner, I click the button on the customer subscription to manage the resources: https://login.microsoftonline.com/testpocazureeng.onmicrosoft.com/oauth2/v2.0/authorize?client_id=c44b4083-3bb0-49c1-b47d-974e53cbdf3c&response_mode=form_post&response_type=code+id_token&scope=https%3a%2f%2fmanagement.core.windows.net%2f%2fuser_impersonation+openid+email+profile&state=OpenIdConnect.AuthenticationProperties%3d&redirect_uri=https%3a%2f%2fportal.azure.com%2fsignin%2findex%2f%40testpocazureeng.onmicrosoft.com%3ffeature.refreshtokenbinding%3dtrue%26feature.snivalidation%3dtrue%26feature.usemsallogin%3dtrue&site_id=501430&client-request-id=04da22b0-9b60-4be9-978a-735caa4c5d85&x-client-SKU=ID_NET&x-client-ver=1.0.40306.1554
After that I am redirected to the standard MS login form, where I insert the partner credentials, and then it redirects me to the azure portal in the customer directory logged as the partner.
So, using the interface I've to do an interactive login using the partner credentials, and I can't use the partner credentials for an interactive login into an automated process.
@manuelmazzuola I'm not very familiar with Partner center. The error that you shared - "The client 'admin@testpocazureeng.onmicrosoft.com' with object id '28cfc43b-2946-49aa-9cc0-f718a441d27e' does not have authorization to perform action 'Microsoft.Resources/subscriptions/providers/read' over scope '/subscriptions/730c3e12-f5d9-401b-bb47-3b7497f310db' or the scope is invalid. If access was recently granted, please refresh your credentials."
makes me think that you are either requesting access to the wrong scope or have not granted the right permissions in the portal.
Not sure how much I can help, as this doesn't seem so much to be an MSAL question as much as a question on how to register the application and provide the right permissions for your specific scenario. Does Partner center provide any guidance on how to manage authorization for what you are trying to accomplish? Have you tried asking them this question?
Didn't hear back. Feel free to reopen if you have any other questions.
I found it. This is the flow:
Get an authentication code going to the following url and use the partner credentials: https://login.windows.net/PARTNER_TENANT/oauth2/authorize?client_id=NATIVE_MULTI_TENANT_APP_ID&response_type=code&response_mode=query&resource=https://api.partnercenter.microsoft.com&prompt=consent
Now get the refresh token executing the following form POST
:
http -f POST https://login.microsoftonline.com/PARTNER_TENANT/oauth2/token
grant_type=authorization_code
client_id=NATIVE_MULTI_TENANT_APP
client_secret="SECRET"
code="AUTH_CODE"
redirect_uri="https://login.microsoftonline.com/common/oauth2/nativeclient"
now with the refresh token I can issue azure access tokens for my customers like that:
http -f POST https://login.windows.net/CUSTOMER_TENANT/oauth2/token
grant_type=refresh_token
client_id=NATIVE_MULTI_TENANT_APP_ID
client_secret="SECRET"
scope="openid"
refresh_token="REFRESH_TOKEN"
resource="https://management.azure.com"
and use the access token returned to call the azure api, eureka!
@sangonzal I can't reproduce the last POST
used to retrieve an azure token using the msal4j library, I thought I had to use the ConfidentialClientApplication
and the RefreshTokenParameters
but there isn't a builder that accept the resource
field as in the above request.
@sangonzal And I know that is the old oauth2 endpoint, it's not the v2 version, but the secure application model documentation uses the old one https://docs.microsoft.com/en-us/partner-center/develop/enable-secure-app-model And all the examples on the internet use the old oauth2 endpoint.
Probably I have to switch to adal4j
@manuelmazzuola two notes:
acquireTokenSilently
. You'll see that the AuthenticationResult
object does not expose RefreshToken
. More instructions on how you might do this below. It seems like what you want to do is:
1) Use getAuthorizationRequestUrl()
to build the authorization code url. Navigate to the url, have th user input their creds, and then once AAD redirects back to your app, your parse the response.
2) Use the authorization code returned in step 1 to acquire a token for resource https://api.partnercenter.microsoft.com
via acquireToken(AuthorizationCodeParameters)
.
3) The ConfidentialClientApplication
object will then have an account and tokens for resource https://api.partnercenter.microsoft.com
. You can get the account in the cache by using getAccounts()
You would then acquireTokenSilently
with the account and the new scope (for resource "https://management.azure.com"). I haven't tried this myself, but I'm assuming that since the user has not consented to the letting the application access "https://management.azure.com"
, this might fail with an InteractionRequired exception. The solution to this is to let users consent to both resources in step 1, as described here, but unfortunately Java does not have support for this yet. I opened up #218 to track this work. I think you could work around this if you chose to by - prompting the user again via steps 1 and 2, or having the tenant admin give consent to the whole tenant in the portal.
Steps 1-3 are demoed in the web application sample: https://github.com/Azure-Samples/ms-identity-java-webapp
It's not the customer that inputs its cred in the first step, it's the admin agent of the partner center. Step 1 and 2 must be done manually, then when I have a refresh token I store it on a vault. When I need to issue a new token for azure for one of my customers I retrieve the refresh token from the vault and use it in the last step. Ideally, the first two steps should be done once in a lifetime because the refresh token expires in 90 days and it's refreshed when it's used.
I haven't tried this myself, but I'm assuming that since the user has not consented to the letting the application access "https://management.azure.com", this might fail with an InteractionRequired exception.
Yes, the last step fails because of that, the error says the customer has not consented to the application with id ######### to access to his resources. So the workaround is that I show the consent form to the customer too using that url
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
scope=openid+https%3A%2F%2Fmanagement.azure.com%2Fuser_impersonation
&response_type=id_token
&redirect_uri=http%3A%2F%2Flocalhost%3A8081
&prompt=consent
&client_id=d6c3df6a-6470-4791-98cf-fbfe1650b281
&response_mode=query
&nonce=1234
and then if I repeat the last step it works without errors.
Is it possible to acquire a token through the AOBO (Admin-On-Behalf-Of) flow?