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

Forcing login on request to authorization endpoint - without using prompt #2058

Closed fredrik-lundin closed 6 years ago

fredrik-lundin commented 6 years ago

Hi,

I'm in a scenario where our SSO (identity server) has several clients. Different clients requires to be authenticated using different authentication methods.

QUESTION/TL;DR: Can we force identity server to run our login logic when in an already authenticated state, without having the user include prompt="login" in their auth request?

Background: To make this work right now, we are doing the following:

  1. Add a custom property on our ClientConfigs that takes a list of allowed authentication methods (I'll come back why we're not using the built in IdentityProviderRestrictions property)
  2. In our login logic, if we are already in an Authenticated state, we intersect allowed authentication methods with used authentication methods (retrieved via User.GetAuthenticationMethods()) to see if the we get a match. If we do, the authentication is fine and we just redirect back to the return url. Else, we force the user to login again, with a valid authentication method for the current client

It looks something like this (maybe the code explains it better than my words..):

var usedAuthenticatedMethods = User.GetAuthenticationMethods().Select(c => c.Value);
if (client.IsAuthenticatedWithAllowedMethod(usedAuthenticatedMethods))
    return Redirect(returnUrl);

return GoToLogin(returnUrl, client);
  1. When we login again, with a second authentication method, we do a new SignInAsync. We merge the already used with the new authentication method into the authenticationMethods parameter. This allows us to add more authentication methods to the current SSO session

Forcing login on request to authorization endpoint To make this work, we obviously need to be able to run our login logic with every authorization request. Right now, the only solution we have found for this is to tell all our clients to include prompt="login" in their auth requests.

We would really like to find a way to trigger this behavior without relying on our clients having to include the prompt flag.

IdentityProviderRestrictions and why we can't use it Identity server already supports a similar scenario with the IdentityProviderRestrictions property on the Client class. Though the logic for checking that property is to see if the currentIdp is in the IdentityProviderRestrictions list. This is not what we want, since the currentIdp is not the list of used authentication methods, but just a single instance of the idp used. From the source code (link to code):

// check current idp
var currentIdp = request.Subject.GetIdentityProvider();

....

// check external idp restrictions if user not using local idp
else if (request.Client.IdentityProviderRestrictions != null && 
     request.Client.IdentityProviderRestrictions.Any() && 
    !request.Client.IdentityProviderRestrictions.Contains(currentIdp))
{
    Logger.LogInformation("Showing login: User is logged in with idp: {idp}, but idp not in client restriction list.", currentIdp);
    return new InteractionResponse { IsLogin = true };
}
leastprivilege commented 6 years ago

You can derive from our default authorize interaction generator - and always trigger login. Don't have a sample - but search for an interface with Interaction in it ;)

fredrik-lundin commented 6 years ago

Thanks for a very quick response and a great product!

Following your suggestion worked fine for us. Here is a really quick explanation of we solved it, if someone finding the issue in the future would be interested :)

  1. Derived from AuthorizeInteractionResponseGenerator and overrode the ProcessLoginAsync method
public class ValidateAuthenticationMethodsAuthorizeInteractionResponseGenerator : AuthorizeInteractionResponseGenerator
{
    public ValidateAuthenticationMethodsAuthorizeInteractionResponseGenerator(
        ILogger<AuthorizeInteractionResponseGenerator> logger, 
        IdentityServerOptions options, 
        IConsentService consent, 
        IProfileService profile,
        IConfiguration configuration)
        : base(logger, options, consent, profile)
    {    }

    protected override Task<InteractionResponse> ProcessLoginAsync(ValidatedAuthorizeRequest request)
    {
        if (!request.Subject.IsAuthenticated())
        {
            return Task.FromResult(new InteractionResponse { IsLogin = true });
        }

        return ValidateUsedAuthenticationMethods(request); // Custom logic
    }

   ....
}
  1. Replace the default Identity Server implementation of IAuthorizeInteractionResponseGenerator with your custom implementation in DI
services.AddIdentityServer(...);

var validateAuthenticationMethodsResponseGenerator = new ServiceDescriptor(
    typeof(IAuthorizeInteractionResponseGenerator), 
    typeof(ValidateAuthenticationMethodsAuthorizeInteractionResponseGenerator), 
    ServiceLifetime.Transient);
services.Replace(validateAuthenticationMethodsResponseGenerator);
PresidentCamacho commented 5 years ago

@fredrik-lundin

Hi, I implemented your code in your second post but it doesnt seems like ProcessLoginAsync gets executed.

Hamels commented 4 years ago

Here is an example https://stackoverflow.com/questions/56327921/always-enter-credentials-without-prompt-login-in-identityserver4

lock[bot] commented 4 years ago

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