2

I'm trying to have different login pages based on the client_id.

Use case : My default login page is a classic username/password type login, but for a specific client_id, the login page asks for 3 different infos that are found one a piece of paper that he received in the mail (sent by a third party). Once i have these 3 infos, i can validate and find the associated user.

Technicals : So far, i have made it so that once IdentityServer4 redirects /connect/authorize to it's default login route (/account/login), i then redirect to my second login based on the client_id. It works but it is not elegant at all (feels hackish). I'm sure there is a better way to achieve this, probably thru a middleware that would redirect from connect/authorize to my second login page, directly ?

Any ideas / tips ?

Carl Quirion
  • 765
  • 5
  • 25

2 Answers2

6

On the very initial Login call to IdentityServer, you call:

/// <summary>
    /// Show login page
    /// </summary>
    [HttpGet]
    public async Task<IActionResult> Login(string returnUrl)
    {
        // build a model so we know what to show on the login page
        var vm = await accountService.BuildLoginViewModelAsync(returnUrl);

        // some more code here

        return View(vm);
    }

In the called accountService.BuildLoginViewModelAsync, you have var context = await interaction.GetAuthorizationContextAsync(returnUrl); and in this context you have the clientId. You can extend the LoginViewModel class to include some custom property (of your own) and based on this property, in the AccountController, to return a different view. Then all you need is in the Views folder to create your specific view.

By this way, you can have as many views as you want.

m3n7alsnak3
  • 3,026
  • 1
  • 15
  • 24
  • Yeah, that's similar to what I already had going on, but instead of extending and putting unrelated stuff together, I created a new ViewModel, new View and i redirected to my other login... I would have prefered a way to make it so that Authorize redirects to the proper Login instead of having to do it inside the first one. But yeah, that solution indeed works. – Carl Quirion Jan 24 '18 at 18:42
  • But it hits the method that I mentioned, before hitting the Authorize call. This is the place to do it. This is what renders the login page. If you don't implement some logic, it renders the default one, if you make some changes - it renders according to your changes. There is no second redirect etc. – m3n7alsnak3 Jan 24 '18 at 19:06
1

Instead of creating hard coded separate views I simply use the appsettings.json file and specify different client configurations for each clientId. This way I can easily edit the file in the future any time there is a new client without having to re-deploy.

Then within the AccountController Login method I set the title and image of the current LoginViewModel (you have to add Title and Image to the LoginViewModel class) by matching the current clientid to a matching object within the appsettings.json file. Then set the ViewBag.Title and ViewBag.Image right before returning the view.

For information on how to wire-up the appsettings see my answer on this SO article

in the BuildLoginViewModelAsync(string returnUrl) method within the AccountController I do the following:

if (context?.ClientId != null)
{
    try
    {
        _customClients.Value.ForEach(x => {
            if (x.Name == context.ClientId)
            {
                title = x.Title;
                image = x.Image;
            }
        });
    }
    catch (Exception){}
   ...
}

Here is my login method within the AccountController:

[HttpGet]
public async Task<IActionResult> Login(string returnUrl)
{
    // build a model so we know what to show on the login page
    var vm = await BuildLoginViewModelAsync(returnUrl);

    if (vm.IsExternalLoginOnly)
    {
        // we only have one option for logging in and it's an external provider
        return RedirectToAction("Challenge", "External", new { provider = vm.ExternalLoginScheme, returnUrl });
    }
    ViewBag.Title = vm.Title;
    ViewBag.Image = vm.Image;
    return View(vm);
}

Then in the _Layout.cshtml I use this;

@using IdentityServer4.Extensions
@{
    string name = null;
    string title = "SomeDefaultTitle";
    string image = "~/somedefaulticon.png";
    if (!true.Equals(ViewData["signed-out"]))
    {
        name = Context.User?.GetDisplayName();
    }
    try
    {
        title = ViewBag.Title;
        image = ViewBag.Image;
    }
    catch (Exception)
    {
    }
}

later in the razor I use @title or @image wherever I need either.

Post Impatica
  • 14,999
  • 9
  • 67
  • 78