IdentityServer / IdentityServer4

OpenID Connect and OAuth 2.0 Framework for ASP.NET Core
https://identityserver.io
Apache License 2.0
9.23k stars 4.02k forks source link

Support for 'id_token_hint' to log in with an existing token #5427

Closed ramondeklein closed 2 years ago

ramondeklein commented 2 years ago

The OpenID Connect specification states in 3.1.2.1:

id_token_hint

OPTIONAL. ID Token previously issued by the Authorization Server being passed as a hint about the End-User's current or past authenticated session with the Client. If the End-User identified by the ID Token is logged in or is logged in by the request, then the Authorization Server returns a positive response; otherwise, it SHOULD return an error, such as login_required. When possible, an id_token_hint SHOULD be present when prompt=none is used and an invalid_request error MAY be returned if it is not; however, the server SHOULD respond successfully when possible, even if it is not present. The Authorization Server need not be listed as an audience of the ID Token when it is used as an id_token_hint value. If the ID Token received by the RP from the OP is encrypted, to use it as an id_token_hint, the Client MUST decrypt the signed ID Token contained within the encrypted ID Token. The Client MAY re-encrypt the signed ID token to the Authentication Server using a key that enables the server to decrypt the ID Token, and use the re-encrypted ID token as the id_token_hint value.

It seems that Identity Server 4 doesn't support this (optional) feature, but it can be very convenient to reauthenticate using the ID token. This issue was already raised in #4969 by @AtomicSimon, but it has been closed and marked as wontfix. Especially in hybrid-scenarios where you can't trust on cookies (i.e. when dealing with mobile apps and websites), this could be a great option to enable SSO between different environments.

Solution

I think I came up with a solution that uses a custom user session that looks like this:

public class CustomUserSession : DefaultUserSession
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ITokenValidator _tokenValidator;

    public CustomUserSession(IHttpContextAccessor httpContextAccessor, ITokenValidator tokenValidator, IAuthenticationHandlerProvider handlers, IdentityServerOptions options, ISystemClock clock, ILogger<IUserSession> logger) : base(httpContextAccessor, handlers, options, clock, logger)
    {
        _httpContextAccessor = httpContextAccessor;
        _tokenValidator = tokenValidator;
    }

    public override async Task<ClaimsPrincipal> GetUserAsync()
    {
        var httpContext = _httpContextAccessor.HttpContext;
        var idTokenHint = httpContext?.Request.Query["id_token_hint"];
        if (!string.IsNullOrEmpty(idTokenHint))
        {
            var result = await _tokenValidator.ValidateIdentityTokenAsync(idTokenHint);
            if (!result.IsError)
            {
                var id = new ClaimsIdentity(result.Claims, "IdentityServer4", JwtClaimTypes.Name, JwtClaimTypes.Role);
                return new ClaimsPrincipal(id);
            }
        }

        return await base.GetUserAsync();
    }
}

This custom IUserSession can be registered using:

services.AddIdentityServer(options => { ... })
        .AddUserSession<CustomUserSession>();

The DefaultUserSession attempts to fetch the user based on the cookie, but I intercept this call and look at the id_token_hint query parameter. If it's set, then the ID token is validated and a claims principal is returned. It seems to work well and it's great to see the extensibility of IS4 due to its nice interface-oriented design.

I think it's secure, because I do proper ID token validation (using the built-in validator), but I may have missed something. I could create a pull-request and integrate it in IS4, but because this project seems to be in maintenance mode, I'm not sure if such a PR would be accepted.

maxhelskens commented 2 years ago

@ramondeklein - Thanks for sharing your solution. This seems to work for me, except for logging out. When I call the end session endpoint I get redirected to account/logout and then I get redirected to /Account/Login?ReturnUrl=%2FAccount%2FLogout%3FlogoutId%3DCfDJ8D...

I think it is because their is no cookie so the [autorize] attribute on the logout endpoint thinks the user is not authenticated.

leastprivilege commented 2 years ago

Important update

This organization is not maintained anymore besides critical security bugfixes (if feasible). This organization will be archived when .NET Core 3.1 end of support is reached (3rd Dec 2022). All new development is happening in the new Duende Software organization.

The new Duende IdentityServer comes with a commercial license but is free for dev/testing/personal projects and companies or individuals making less than 1M USD gross annnual revenue. Please get in touch with us if you have any question.

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Questions are community supported only and the authors/maintainers may or may not have time to reply. If you or your company would like commercial support, please see here for more information.

github-actions[bot] commented 2 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.