Rich Twitter Digits/Google Auth with OpenIdDictServer

1.3k views Asked by At

Our app requires sign-in by either mobile number or Google. We are planning to Twitter Digits for mobile number authentication.

The flow of registration and authentication as I understand is as below:

  1. Mobile app does rich authentication with Twitter Digits or Google Sign In (it’s better user experience for the user to do rich auth instead of opening a web browser tab). Twitter Digits / Google Sign In returns Identity Token.

  2. Mobile app calls AuthServer to SignIn and presents Identity Token.

  3. Identity server validates the presented Identity Token with Digits service or Google Auth Service.

  4. Once verified, AuthServer would try to find the user, if not present it will create one.

  5. AuthServer returns Access Token to the mobile app.

Now, I am looking for options to implement step #3-4.

Currently, what I have done is to modify token end-point code that takes in username as phone number or email address and password sent as Google/Twitter Digits ID token. Now, since auth server needs to know that the password sent is actually a token that needs to be verified with a third party service, I worked around it by sending service name “Digits” or “Google” in TokenHint.

This works very well, but I am wondering what is the cleanest way to support what I am trying to achieve.

1

There are 1 answers

1
Kévin Chalet On BEST ANSWER

This works very well, but I am wondering what is the cleanest way to support what I am trying to achieve.

I'd personally go with a custom grant type:

[HttpPost("~/connect/token")]
[Produces("application/json")]
public IActionResult Exchange(OpenIdConnectRequest request)
{
    if (request.GrantType == "urn:ietf:params:oauth:grant-type:google_identity_token")
    {
        // Reject the request if the "assertion" parameter is missing.
        if (string.IsNullOrEmpty(request.Assertion))
        {
            return BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.InvalidRequest,
                ErrorDescription = "The mandatory 'assertion' parameter was missing."
            });
        }

        // Create a new ClaimsIdentity containing the claims that
        // will be used to create an id_token and/or an access token.
        var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);

        // Manually validate the identity token issued by Google,
        // including the issuer, the signature and the audience.
        // Then, copy the claims you need to the "identity" instance.

        // Create a new authentication ticket holding the user identity.
        var ticket = new AuthenticationTicket(
            new ClaimsPrincipal(identity),
            new AuthenticationProperties(),
            OpenIdConnectServerDefaults.AuthenticationScheme);

        ticket.SetScopes(
            OpenIdConnectConstants.Scopes.OpenId,
            OpenIdConnectConstants.Scopes.OfflineAccess);

        return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
    }

    return BadRequest(new OpenIdConnectResponse
    {
        Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
        ErrorDescription = "The specified grant type is not supported."
    });
}

Note that you'll also have to enable it in the OpenIddict options:

// Register the OpenIddict services.
services.AddOpenIddict()
    // Register the Entity Framework stores.
    .AddEntityFrameworkCoreStores<ApplicationDbContext>()

    // Register the ASP.NET Core MVC binder used by OpenIddict.
    // Note: if you don't call this method, you won't be able to
    // bind OpenIdConnectRequest or OpenIdConnectResponse parameters.
    .AddMvcBinders()

    // Enable the token endpoint.
    .EnableTokenEndpoint("/connect/token")

    // Enable the refresh token flow and a custom grant type.
    .AllowRefreshTokenFlow()
    .AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_identity_token")

    // During development, you can disable the HTTPS requirement.
    .DisableHttpsRequirement();

When sending a token request, make sure to use the right grant_type and to send your id_token as the assertion parameter, and it should work.

Here's an example using Facebook access tokens:

Be extremely careful when implementing the token validation routine, as this step is particularly error-prone. It's really important to validate everything, including the audience (otherwise, your server would be vulnerable to confused deputy attacks).