Microsoft-authentication-library-for-dotnet: Unable to pass both username/password and client_secret during authentication

Created on 11 Feb 2021  路  14Comments  路  Source: AzureAD/microsoft-authentication-library-for-dotnet

Looking for a way to generate a token while passing in both username/password as well as client_secret. In the PowerShell following code works well

$url = 'https://login.microsoftonline.com/{tenant}/oauth2/token'
$dict = @{}
$dict.Add('resource','https://graph.microsoft.com/')
$dict.Add('grant_type','password')
$dict.Add('client_id','{clientid}')
$dict.Add('username','{username}')
$dict.Add('password','{password}')
$dict.Add('client_secret','{client secret}')
$body = Invoke-RestMethod -Method Post -Uri $url -UseBasicParsing -Body $dict

Is there the ability to craft the same request using MSAL .NET?

Trying following code while passing extra query parameters has not produced the desired results, as these are adding QueryParameters and not the Body Parameters

result = await App.AcquireTokenByUsernamePassword(scopes, username, password)
.WithExtraQueryParameters(extra)

By Design answered question ConfidentialClient ConfidentialClient-ROPC

All 14 comments

AcquireTokenByUsernamePassword applies to Public client application. MASL.NET does not support it on confidential client applications (which would have a client secret or a certificate). We encourage you to use the on behalf of flow instead.

Username/password is not secure (the app needs to have the password), and has many limitations (no support for conditional access for instance). Can you please tell us more about your scenario?
cc: @hpsin

@jmprieur - In my scenario, I am working with a graph client to call Azure AD entitlement management API. These endpoints only support delegated (work or school account) permissions, therefore username/passwords are required.

More details: https://docs.microsoft.com/en-us/graph/api/accesspackageassignmentrequest-post

Including secret in addition to username/password helps to avoid switching application "Allow public client flows" to on. Did I understand correctly that MSAL.NET doesn't support this scenario?

The preferred mechanism to get a user token is the authorization flow, i.e. display a browser to the user and allow them to login. This interactive flow is much better because it allows users to be challenged by MFA, provide consent etc.

Username / Password flow is only supported on public client (i.e. without a certificate) and for very limited use. We strongly discourage it. The only scenario where this flow is acceptable is in CI pipelines, where a "system user" needs to perform certain operations, and there cannot be interaction. In this case, the tenant admin would ensure this system user is not MFA challenged. The credentials of the system user are typically stored in a secure location like KeyVault and rotated periodically.

@bgavrilMS reading your response, it's a perfect description of my scenario. Unfortunately, authorization flow is not an option, as code is executing from within Azure Function and there is no user interaction. System User is used to performing the operation, MFA is taken care of and KeyVault is in use :)

The only difference was that we wanted to keep "Allow public client flows" as off, as such scenario works via HTTP request, but not through MSAL. I am realizing that MSAL doesn't have the ability to craft a similar request to what is shown above using power shell and I wanted to get some confirmation of that to make sure I've not missed anything.

It's a valid scenario, I'll let the others reply here with their thoughts.

CC: @henrik-me

I'm assuming this will fail for users which have MFA enabled and possibly also several CA policies, thus even if we enable this scenario it may appear broken. We have been discussing supporting this flow before and see no big issues with it, as the client have to keep the client secret - secret anyway, however we don't have an out of the box solution for storing username/pwd in keyvault, which we likely should to ensure there are no secrets flowing around. @jmprieur ?

An Azure Function should be possible to run in the user context and thus leverage obo, @jmprieur, @jennyf19 ?

@vkuzin I'm not entirely clear on your scenario, but I agree with @jmprieur that ROPC is restricted to public client applications, which do not have access to a secret, see more info in the OAuth spec.

However, if you are using Azure Functions, which, from the point of view of AAD apps, is like a web API, which is why there is no user interaction, you are able to still get information about the sign-in user and call downstream APIs or Graph, from the Azure Function. You can see in this sample, we are making a request to graph and have set it up in Startup.cs by enabling Microsoft.Identity.Web and calling a downstream API, which is Graph (appsettings.json).

We have a client which calls this Azure Function here and here. The user would sign in to the client, which would then call the Azure Function. As mentioned above, the ROPC flow should really not be used, and only in very specific use cases.

Also, for OBO, the web API (the Azure Function) needs to be protected and Microsoft Identity Web would ensure that as well.

If you want a protected Azure Function starter, you can use the templates in Microsoft Identity Web 1.6.0 release, just scroll down for the section on Azure Functions (func2).

@jennyf19 thank you for your reply and additional details, it is much appreciated. It looks like in provided scenario and examples, users still require to have interactive login in the client app, and then obtained authorization token is passed to the Azure Function. In my scenario, it will not work, as there is no client. Azure Function is triggered via Service Bus Trigger without any user interaction and an authorization token must be generated in the Azure Function itself.

At this point, I see only a few ways to go forward. 1) Either switch the Azure AD app to allow public client flows and use credentials of the service account or 2) Generate a token with a private key and credentials at the same time via a direct HTTP call (which per the above comment, even though is working at this time, might not fully comply with Oauth specs)

Interestingly enough while digging through the OAuth spec Access Token Request it talks about that confidential clients should include client authentication (clientid & secret) for the grant type "password". I am guessing this is the scenario I was looking to do with MSAL.

@vkuzin : what about using AcquireTokenForClient, with the EntitlementManagement.ReadWrite.All application permissions?

@jmprieur application permissions for EntitlementManagement.ReadWrite.All have not been supported. However, I've just looked at the documentation, and it looks like support was added 4 days ago. I will try it out first thing in the morning and report back. This is exciting.

Great! I'm glad I checked again.

Overall this new application permission worked. Some engineers with knowledge of the subject mentioned that it is currently tenant-level permission. Depending on the organization's size and justification, it might be hard to convince admins to consent.

As far as MSAL, it would be nice to see confidential clients' support for the grant_types password sometime in the future.

Thank you all for your thoughts and comments.

@hpsin
Do you want to confirm that we don't want, in the future, to support Username password for confidential clients in MSAL libraries?

I encountered the same issue when creating a M365/SPO provisioning engine using Azure Functions interacting with Microsoft Graph. Quite a few Graph endpoints don't support Application Permissions (i.e. Planner https://docs.microsoft.com/en-us/graph/api/planner-post-plans?view=graph-rest-1.0&tabs=http ) and require a Delegated permissions token.
Without MSAL.NET this is achieved with a http request using grant_type = password
```c#
string endPoint = string.Format("https://login.microsoftonline.com/{0}/oauth2/token", delPermissionCred.TenantDomain);
var values = new List>()
{
new KeyValuePair("grant_type", "password"),
new KeyValuePair("resource", "https://graph.microsoft.com"),
new KeyValuePair("client_id", delPermissionCred.ClientID),
new KeyValuePair("username", delPermissionCred.Username),
new KeyValuePair("password", delPermissionCred.Password),
new KeyValuePair("client_secret", delPermissionCred.ClientSecret)
};

var request = new HttpRequestMessage(HttpMethod.Post, endPoint)
{
Content = new FormUrlEncodedContent(values)
};
var response = await client.SendAsync(request);
```

The advantage of grant_type=password is that it still requires a client_secret (or client_assertion) so no normal user will be able to get a token as they don't know the secret. See https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc#authorization-request for detail about this part, in particular:
| Parameter | Condition | Description |
| --- | --- | --- |
| client_secret | Sometimes required | If your app is a public client, then the client_secret or client_assertion cannot be included. If the app is a confidential client, then it must be included. |
|client_assertion | Sometimes required | A different form of client_secret, generated using a certificate. See certificate credentials for more details. |

This is all backend code with no user interaction (and the actual "user" is a service account that is used only because Microsoft Graph forces us to use username and password. If Microsoft Graph support Application permissions for all endpoint, none would need to go through this).

It would be nice if MSAL.NET implemented this scenario which is required by Microsoft Graph. If you don't want to support it, you could just write something in the documentation about scenarios where this will not work (MFA, local AD account, etc).

Was this page helpful?
0 / 5 - 0 ratings