This is the third part of a series of blog posts related to Azure AD best practices. They are all related to a talk I gave at Tech Days Finland as well as in the Microsoft Identity Developer Community Office Hours.
In this part we will look at the OAuth Resource Owner Password Credentials grant flow, one of the possible ways to authenticate a user and access services on their behalf in Azure AD. This is a flow that you should avoid using. I have a sample app on GitHub that shows its usage, but this app was used to demonstrate the downfalls of the ROPC flow. You can find it here: https://github.com/juunas11/7-deadly-sins-in-azure-ad-app-development/tree/master/RopcLogin.
What is the ROPC grant flow and why does it exist
The Resource Owner Password Credentials grant flow, aka the ROPC flow or the password flow, is an OAuth authorization flow. It allows an application to pass along a user's credentials to acquire tokens to call APIs.
You can see an example of its usage in that app on GitHub:
private async Task<(string idToken, string accessToken)> AcquireTokensWithRopc(LoginModel model)
{
var req = new HttpRequestMessage(HttpMethod.Post, _authSettings.TokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "password",
["client_id"] = _authSettings.ClientId,
["client_secret"] = _authSettings.ClientSecret,
["username"] = model.Username,
["password"] = model.Password,
["scope"] = $"openid profile {_authSettings.EmployeeApiAppIdUri}/.default"
})
};
var res = await HttpClient.SendAsync(req);
string json = await res.Content.ReadAsStringAsync();
if (!res.IsSuccessStatusCode)
{
throw new Exception("Failed to acquire token: " + json);
}
var result = (JObject)JsonConvert.DeserializeObject(json);
return (result.Value<string>("id_token"), result.Value<string>("access_token"));
}
Now this feels very different from typical OAuth flows which separate a user's credentials from the applications. In the ROPC flow the user's password is passing through the application. So why does it exist?
It's meant to be a migration path for legacy applications. It allows for a legacy client to be upgraded in stages to more modern flows. It should never be used in new applications.
Why you should not use the ROPC flow
In the sample app a custom login page is made that calls Azure AD's token endpoint with the user's credentials as well as the app's credentials. The sample app is on GitHub: https://github.com/juunas11/7-deadly-sins-in-azure-ad-app-development/tree/master/RopcLogin. The approach there does work for regular cloud-only users (local and guest).
However, it does not work for:
- Users with Multi-Factor Authentication
- Users who have not given consent to the scopes required
- Users with an expired password
- Users synced from on-premises AD
- Personal Microsoft or Google accounts
Do you really want to disable MFA for a custom login page? As well as taking on all of the other downsides? Most likely you don't.
Single sign-on of course will not work when using ROPC, since it relies on setting a cookie on the user when they sign in using the AAD login page.
One of the uses I have found for the ROPC flow is automated testing of APIs. In the tests you can create a test user account in a test Azure AD tenant, and acquire tokens to call your API on behalf of that user. A lot of emphasis on test user and test tenant. This allow you to test authorization quite nicely.
I've also seen usage of ROPC to call APIs which do not offer application permissions. Meaning the API must always be called with a user context. But in the case of apps that run some kind of scheduled tasks in a background process, that's a bit difficult. There is a way to do it more securely that involves having the user authorize your background app once. After that your app can use the refresh token to get new tokens for that user. That isn't 100% reliable of course since refresh tokens can get revoked. In that case you would be unable to get new tokens with that refresh tokens until the user authorizes your app again. So in case an API doesn't provide app permissions and the process's success is critical for business, you might not have any other choice than ROPC. If the API does provide app permissions, I'd choose them any day over ROPC.
Please don't use the ROPC flow to build custom login pages. These requirements are almost always misguided. If your application allows Office 365 login, then the users should log in to Office 365. By having your users enter their passwords into your app, you are training them to be phished.