ASPNet Core Identity Server 4 – Resource Owner Password Customization

Overview: Identity Server 4 & Resource Owner Password

Most modern applications look more or less like this:

IdentityServer is middleware that adds the spec compliant OpenID Connect and OAuth 2.0 endpoints to an arbitrary ASP.NET Core application.

Grant types are a way to specify how a client wants to interact with IdentityServer. There are many types you can take a look here. I wanna focus on 2 common types almost applications applied.

  • Resource owner password: the grant type allows to request tokens on behalf of a user by sending the user’s name and password to the token endpoint. This is always used for Server App.
  • Client credentials: the simplest grant type and is used for server to server communication – tokens are always requested on behalf of a client, not a user. This is often used for Native App.

Scenario

I need to develop an application meets requirements below:

  • Back Office: Admin User can access via username/password.
  • Mobile App: Navative App User can access via phone number and passcode.
  • All requests to Resource Server must be authenticated via token from Identity Server issued.
  • Resource Server must identify users made requests.

The problem is Identity Server uses Client Credential grant type to authenticate with Native App User, the Resource Server will not identify who user is. Identity Server issues token without user information due to using Client Credential.

To identify user the Identity Server have to use Resource Owner Password grant type as treated with back office users.

Customization

Implementing ResourceOwnerPasswordValidator

namespace IdentityServer
{
    public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
    {
        public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
        {
            TestUser user = null;

            // verify username password
            user = Config.GetUsers().FirstOrDefault(p => p.Username == context.UserName && p.Password == context.Password);
            if (user != null)
            {
                context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password);
            } 
            else
            {
                // verify phone number and auth code
                var authCode = context.Request.Raw["AuthCode"];
                var phoneNumber = context.Request.Raw["PhoneNumber"];

                user = Config.GetUsers().FirstOrDefault(p => p.Claims.Any(k => k.Type == "AuthCode" && k.Value == authCode) 
                    && p.Claims.Any(k => k.Type == ClaimTypes.MobilePhone && k.Value == phoneNumber));
                if (user != null)
                    context.Result = new GrantValidationResult(user.SubjectId, OidcConstants.AuthenticationMethods.Password);
            }

            return Task.FromResult(0);
        }
    }
}

Customizing request from client

// using token client to request token
var clientId = "ro.client";
var clientSecret = "secret";
var tokenClient = new TokenClient(disco.TokenEndpoint, clientId, clientSecret);

// get token by phone/authcode
var extra = new Dictionary<string, string> { { "AuthCode", "1111" }, { "PhoneNumber", "0901111111" } };
var tokenClientResponse = await tokenClient.RequestResourceOwnerPasswordAsync("xxx", "xxx", extra: extra);

//// get token by username/password
//var tokenClientResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password");

Full sample source code: https://github.com/trietdvn/custom-resource-owner-password

You can also take a look some samples here
https://github.com/IdentityServer/IdentityServer4.Samples/tree/master/Quickstarts