MicrosoftDocs / azure-docs

Open source documentation of Microsoft Azure
https://docs.microsoft.com/azure
Creative Commons Attribution 4.0 International
10.2k stars 21.36k forks source link

ObO flow for service principal? #53334

Closed zvrba closed 4 years ago

zvrba commented 4 years ago

I have a WebAPI that uses ObO flow to propagate user identities throughout the system when calling further APIs (e.g., storage and Azure SQL database). When the API is invoked by a User, everything works as expected. When the API is invoked by a Service Principal, ObO token acquisition fails with "AADSTS90083: Request is unsupported. (invalid_grant)".

In essence, the WebAPI must support two scenarios: 1) being called by a desktop or web app [works, as the user is present and ObO token acquisition succeeds], 2) being called by a daemon app (which authenticates itself using client credentials flow) [fails]. In both scenarios, ObO should be used for accessing downstream services.

Can you provide some guidance about supporting this scenario?

My current workaround would be to create "daemon users" (User object in Graph) and use ROPC flow instead of client credentials.


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

SwathiDhanwada-MSFT commented 4 years ago

@zvrba Thanks for your comment. We will review the issue and get back to you.

souravmishra-msft commented 4 years ago

@zvrba, I guess in the second step when you are trying to submit the access token issued to WebAPI-1 back to AAD and trying to request another access token for WebAPI-2 you need to mention the grant_type as "urn:ietf:params:oauth:grant-type:jwt-bearer"

The request should look something like:

//line breaks for legibility only

POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&client_id=2846f71b-a7a4-4987-bab3-760035b2f389
&client_secret=BYyVnAt56JpLwUcyo47XODd
&assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InowMzl6ZHNGdWl6cEJmQlZLMVRuMjVRSFlPMCJ9.eyJhdWQiOiIyODQ2ZjcxYi1hN2E0LTQ5ODctYmFiMy03NjAwMzViMmYzODkiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3YyLjAiLCJpYXQiOjE0OTM5MjA5MTYsIm5iZiI6MTQ5MzkyMDkxNiwiZXhwIjoxNDkzOTI0ODE2LCJhaW8iOiJBU1FBMi84REFBQUFnZm8vNk9CR0NaaFV2NjJ6MFFYSEZKR0VVYUIwRUlIV3NhcGducndMMnVrPSIsIm5hbWUiOiJOYXZ5YSBDYW51bWFsbGEiLCJvaWQiOiJkNWU5NzljNy0zZDJkLTQyYWYtOGYzMC03MjdkZDRjMmQzODMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJuYWNhbnVtYUBtaWNyb3NvZnQuY29tIiwic3ViIjoiZ1Q5a1FMN2hXRUpUUGg1OWJlX1l5dVZNRDFOTEdiREJFWFRhbEQzU3FZYyIsInRpZCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NyIsInV0aSI6IjN5U3F4UHJweUVPd0ZsTWFFMU1PQUEiLCJ2ZXIiOiIyLjAifQ.TPPJSvpNCSCyUeIiKQoLMixN1-M-Y5U0QxtxVkpepjyoWNG0i49YFAJC6ADdCs5nJXr6f-ozIRuaiPzy29yRUOdSz_8KqG42luCyC1c951HyeDgqUJSz91Ku150D9kP5B9-2R-jgCerD_VVuxXUdkuPFEl3VEADC_1qkGBiIg0AyLLbz7DTMp5DvmbC09DhrQQiouHQGFSk2TPmksqHm3-b3RgeNM1rJmpLThis2ZWBEIPx662pjxL6NJDmV08cPVIcGX4KkFo54Z3rfwiYg4YssiUc4w-w3NJUBQhnzfTl4_Mtq2d7cVlul9uDzras091vFy32tWkrpa970UvdVfQ
&scope=https://graph.microsoft.com/user.read+offline_access
&requested_token_use=on_behalf_of

Note: In OBO flow the the the first step where you try to get the first token, it always have to be for a user and the eligible flows are:

Client_Credential flow wont work in OBO flow of OAuth. Do let me know if this helps.

zvrba commented 4 years ago

Hi,

you need to mention the grant_type as "urn:ietf:params:oauth:grant-type:jwt-bearer"

This is done: the ObO flow is performed with MSAL.NET and Microsoft.Identity.Web code.

Client_Credential flow wont work in OBO flow of OAuth.

Ok, this is the root cause. Is this a limitation of AAD? Thanks for the explanation.

souravmishra-msft commented 4 years ago

@zvrba, Yeah, OBO doesnt support Client_Credentials flow as it doesnt make sense for it to use Client_Cred flow, since Client_Cred flow gets you an application token where as OBO flow needs a delegated token. Delegated tokens could only be fetched by users. You can also check this article: https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow.

In here you would be able to find a line that states "The idea is to propagate the delegated user identity and permissions through the request chain. "

So you can only use the OAuth flows that help you fetch a delegated token. This is not a limitation of AAD, this is how the flow is defined by the OAuth specifications.

You can find more details here: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-oapx/7a414772-f21c-4822-9856-d0f3904cf843

Hope this helps. Do let us know if any more queries around this. If no more queries, do let us know so that we can close this thread out.

zvrba commented 4 years ago

Delegated tokens could only be fetched by users.

This is the surprising part that doesn't make sense. Or, rather, I interpreted "user" as any kind of authenticated entity ("user" or "service" principal).

From my POV while managing access control, a "user" is a principal that has access to certain resources when it provides a valid token for the resource, and the same holds for a "service" principal. For the purposes of identity and permissions, they behave the same; both can, for example, get a "Contributor" role on a Blob Storage account.

The client application (the one requesting the ObO token) declares permissions for the Storage APIs and is able to impersonate a "user" principal. Assuming admin consent is given for the client application, there does not seem to be anything in the way of impersonating a "service" principal and propagating its identity and permissions. It's surprising that the OAuth2 spec cares about where the token "comes from" when doing the ObO flow. (Or, rather, that AD cares about where the token comes from when both kinds of tokens represent an identity that can be impersonated in principle.)

I tried to use ObO for SP because it would have been simpler for administrative reasons in my setup. I have now worked around the issue by creating a dummy "user" principal in the directory and using ROPC flow.

Thanks for quick replies. You can close the issue.

souravmishra-msft commented 4 years ago

@zvrba, yeah, I understand. This does create a lot of confusion. Its great to know that the app got into a working state now.

Closing this thread for now.

debugaftercoffee commented 3 years ago

@zvrba What did you use for your username and password when using the ROPC Flow with the dummy service principal? I am trying the same thing, and I have try the ClientID and service principal object id as the username, and ClientSecret as the password, but I get the following HTTP 400 on the call to get the token. I appreciate any guidance you can provide.

'AADSTS50034: The user account xxxx does not exist in the yyyyyy directory. To sign into this application, the account must be added to the directory.'

zvrba commented 3 years ago

@zvrba What did you use for your username and password when using the ROPC Flow with the dummy service principal?

You cannot use ROPC with a service principal, you have to use client credentials flow instead.

To use ROPC, you must create an actual User object (Azure portal -> AAD -> Users -> New user). If this account is to be used for automation, there are two things to set after creating the account and setting the initial password:

  1. Do NOT require that the user change password on first login.
  2. Consider setting password expiry to "never".
ade-owasp-sf commented 1 year ago

@zvrba What did you use for your username and password when using the ROPC Flow with the dummy service principal?

You cannot use ROPC with a service principal, you have to use client credentials flow instead.

To use ROPC, you must create an actual User object (Azure portal -> AAD -> Users -> New user). If this account is to be used for automation, there are two things to set after creating the account and setting the initial password:

  1. Do NOT require that the user change password on first login.
  2. Consider setting password expiry to "never".

Great idea. However, I'll update as follows:

  1. While human entity strong password requirement is usually about 10 to 15 chars, the automation user's password must be minimum 32 chars long.
  2. Do NOT require that the user change password on first login.
  3. Set the password to expire and rotate the password at least every 180 days.