Sadly documentation on the implementation of a custom AuthorizeInteractionResponseGenerator in IdentityServer4 is sorely lacking.
I'm trying to implement my own AuthorizeInteractionResponseGenerator because I need a further step of user interaction (after authentication). My scenario is that a single identity (email) can be associated with multiple tenants. So after logon, I need the user to be presented with a list of associated tenants, so that they can choose one.
I have evaluated the source code, and have come up with the the following custom AuthorizeInteractionResponseGenerator:
public class AccountChooserResponseGenerator : AuthorizeInteractionResponseGenerator
{
public AccountChooserResponseGenerator(ISystemClock clock,
ILogger<AuthorizeInteractionResponseGenerator> logger,
IConsentService consent, IProfileService profile)
: base(clock, logger, consent, profile)
{
}
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
{
var response = await base.ProcessInteractionAsync(request, consent);
if (response.IsConsent || response.IsLogin || response.IsError)
return response;
return new InteractionResponse
{
RedirectUrl = "/Organization"
};
}
}
It inherits from the base AuthorizeInteractionResponseGenerator built into IdentityServer4, so that the standard Logon and Consent pages can show. This happens, and then the user is correctly redirected to the /Organization url to select an organization (tenant).
But what then? With the lack of documentation and examples, I'm really struggling to figure out the following two questions:
1) How do I now, having selected a Tenant, indicate to my custom AccountChooserResponseGenerator that my interaction is complete, and that the user can now be redirected back to the Client?
Edit:
Answer to 1: To indicate that the interaction is complete, you I have to return an empty new InteractionResponse(). In my case, a check for the existence of the TenantId claim sufficed, as follows:
if (!request.Subject.HasClaim(c=> c.Type == "TenantId" && c.Value != "0"))
return new InteractionResponse
{
RedirectUrl = "/Organization"
};
return new InteractionResponse();
2) And how can I get information about the selected Tenant to be added to the identity token that IdentityServer4 passes back to the Client?
Edit: Answer to 2: In the Controller Action method that gets executed after selecting a Tenant, I called :
await HttpContext.SignInAsync(User.Claims.Single(r=> r.Type == "sub").Value,
new System.Security.Claims.Claim("TenantId", tenant.Id.ToString()));
return Redirect(ReturnUrl);
...which is an IdentityServer4-provided Extension to HttpContext.
To implement a custom interaction response generator in Identity Server 4 and add information about the selected tenant to the identity token, you can inherit from the base AuthorizeInteractionResponseGenerator and override the ProcessInteractionAsync method. You can also use the IProfileService interface provided by Identity Server 4 to add the tenant information as a claim to the user's identity before the token is issued.
Steps to implement a custom interaction response generator:
Inherit from the base AuthorizeInteractionResponseGenerator and override the ProcessInteractionAsync method:
// Example implementation of ProcessInteractionAsync
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
{
// Your custom logic here
// Indicate that the interaction is complete
return new InteractionResponse();
}
Add information about the selected tenant to the identity token:
// Example implementation of IProfileService
public class CustomProfileService : IProfileService
{
private readonly UserManager<ApplicationUser> _userManager;
public CustomProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var user = await _userManager.GetUserAsync(context.Subject);
if (user != null)
{
var tenantIdClaim = user.Claims.FirstOrDefault(c => c.Type == "TenantId");
if (tenantIdClaim != null)
{
context.IssuedClaims.Add(new Claim("tenant_id", tenantIdClaim.Value));
}
}
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.CompletedTask;
}
}
Register the CustomProfileService with Identity Server 4 in your Startup.cs file:
services.AddTransient<IProfileService, CustomProfileService>();
Tips:
IConsentService and IProfileService injected into your generator to access user profiles and consent data.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With