Search code examples
.netoauth-2.0openiddict.net-7.0

OpenIdDict userinfo endpoint/controller not executed


I am using openiddict and trying to retrieve /userinfo, however, my userinfo controller never executes when debugging, but I do receive a response from the api.

Postman

response:

{
    "sub": "1e56db90-fe77-47a6-b260-941a59bf32e4",
    "iss": "https://localhost:7049/",
    "aud": "postman"
}

program.cs


    builder.Services.AddOpenIddict().AddCore(options =>
    {
        options.UseEntityFrameworkCore().UseDbContext<FaDbContext>();
    })
        .AddServer(options =>
        {
            options
                .AllowClientCredentialsFlow()
                .AllowAuthorizationCodeFlow()
                    .RequireProofKeyForCodeExchange()
                .AllowPasswordFlow()
                .AllowRefreshTokenFlow();
            options.SetTokenEndpointUris("/token")
            .SetAuthorizationEndpointUris("/connect/authorize")
            .SetUserinfoEndpointUris("/connect/userinfo")
            .SetVerificationEndpointUris("/connect/verify");
    
            X509Certificate2 privateKey;
            var bytes = File.ReadAllBytes(builder.Configuration["Auth:PrivateKeyPath"] ?? "");
            privateKey = new X509Certificate2(bytes, "test");
            X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadOnly);
    
            X509Certificate2 encryptionKey;
            var bytes2 = File.ReadAllBytes(builder.Configuration["Auth:EncryptionkeyPath"] ?? "");
            encryptionKey = new X509Certificate2(bytes2, "test");
            X509Store store2 = new X509Store(StoreName.My, StoreLocation.LocalMachine);
            store.Open(OpenFlags.ReadOnly);
    
    
            options
            .AddSigningCertificate(privateKey)
            .AddEncryptionCertificate(encryptionKey).DisableAccessTokenEncryption();
    
            options.SetAccessTokenLifetime(TimeSpan.FromSeconds(60));
            options.SetRefreshTokenLifetime(TimeSpan.FromDays(60));
            options.RegisterScopes(Scopes.Email ,Scopes.Profile, Scopes.Roles, Scopes.OfflineAccess);
    
            options
                    .UseAspNetCore()
                    .EnableTokenEndpointPassthrough()
                    .EnableAuthorizationEndpointPassthrough();
        }
    ).AddValidation(options =>
    {
        options.AddAudiences("faid_client");
        options.UseLocalServer();
        options.UseAspNetCore();
    });

UserInfoController.cs


    public class UserInfoController : Controller
        {
    
            private readonly UserManager<User> _userManager;
    
            public UserInfoController(UserManager<User> userManager)
                => _userManager = userManager;
    
            [Microsoft.AspNetCore.Authorization.Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)]
            [HttpGet("~/connect/userinfo"), HttpPost("~/connect/userinfo")]
            [IgnoreAntiforgeryToken, Produces("application/json")]
            public async Task<IActionResult> Userinfo()
            {
                var user = await _userManager.GetUserAsync(User);
                if (user is null)
                {
                    return Challenge(
                        authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
                        properties: new AuthenticationProperties(new Dictionary<string, string>
                        {
                            [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,
                            [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                                "The specified access token is bound to an account that no longer exists."
                        }));
                }
    
                var claims = new Dictionary<string, object>(StringComparer.Ordinal)
                {
                    // Note: the "sub" claim is a mandatory claim and must be included in the JSON response.
                    [Claims.Subject] = await _userManager.GetUserIdAsync(user)
                };
    
                if (User.HasScope(Scopes.Email))
                {
                    claims[Claims.Email] = await _userManager.GetEmailAsync(user);
                    claims[Claims.EmailVerified] = await _userManager.IsEmailConfirmedAsync(user);
                }
    
                if (User.HasScope(Scopes.Phone))
                {
                    claims[Claims.PhoneNumber] = await _userManager.GetPhoneNumberAsync(user);
                    claims[Claims.PhoneNumberVerified] = await _userManager.IsPhoneNumberConfirmedAsync(user);
                }
    
                if (User.HasScope(Scopes.Roles))
                {
                    claims[Claims.Role] = await _userManager.GetRolesAsync(user);
                }
    
                // Note: the complete list of standard claims supported by the OpenID Connect specification
                // can be found here: http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
    
                return Ok(claims);
            }
        }


Solution

  • You haven't enabled userinfo endpoint passthrough in your server setup so it's still using OpenIddict's default implementations.

    Add

    EnableUserinfoEndpointPassthrough()

    like so:

    options
        .UseAspNetCore()
        .EnableTokenEndpointPassthrough()
        .EnableAuthorizationEndpointPassthrough()
        .EnableUserinfoEndpointPassthrough();
    

    And OpenIddict should pass the userinfo request to your controller action now.