SmartestVEGA
SmartestVEGA

Reputation: 8889

OpenIdConnectEvents OnAuthorizationCodeReceived not firing

I am signing in with Azure AD in my Dotnet core web application, it is supposed to hit OnAuthorizationCodeReceived event, but it's not fired.

Could you please give some light on this?

 public void Configure(string name, OpenIdConnectOptions options)
            {
                JsonFileConfigurationService config = new JsonFileConfigurationService();
                options.ClientId = config.AzureAdClientId;
                options.Authority = $"{config.AzureAdInstance}{config.AzureAdTenantId}";
                options.UseTokenLifetime = true;
                options.CallbackPath = config.AzureAdCallbackPath;
                options.RequireHttpsMetadata = false;
                var allScopes = $"{_azureOptions.Scopes} {_azureOptions.GraphScopes}".Split(new[] { ' ' });
                foreach (var scope in allScopes) { options.Scope.Add(scope); }

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    // Instead of using the default validation (validating against a single issuer value, as we do in line of business apps),
                    // we inject our own multitenant validation logic
                    ValidateIssuer = false,

                    // If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
                    //IssuerValidator = (issuer, securityToken, validationParameters) => {
                    //    if (myIssuerValidationLogic(issuer)) return issuer;
                    //}
                };

                options.Events = new OpenIdConnectEvents
                {
                    OnTicketReceived = context =>
                    {
                        // If your authentication logic is based on users then add your logic here
                        return Task.CompletedTask;
                    },
                    OnAuthenticationFailed = context =>
                    {
                        context.Response.Redirect("/Home/Error");
                        context.HandleResponse(); // Suppress the exception
                        return Task.CompletedTask;
                    },
                    OnAuthorizationCodeReceived = async (context) =>
                    {
                        var code = context.ProtocolMessage.Code;
                        var identifier = context.Principal.FindFirst(config.AzureAdObjectIdentifierType).Value;
                        var memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
                        var graphScopes = _azureOptions.GraphScopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                        var cca = new ConfidentialClientApplication(
                            _azureOptions.ClientId,
                            _azureOptions.BaseUrl + _azureOptions.CallbackPath,
                            new ClientCredential(_azureOptions.ClientSecret),
                            new SessionTokenCache(identifier, memoryCache).GetCacheInstance(),
                            null);
                        var result = await cca.AcquireTokenByAuthorizationCodeAsync(code, graphScopes);

                        // Check whether the login is from the MSA tenant. 
                        // The sample uses this attribute to disable UI buttons for unsupported operations when the user is logged in with an MSA account.
                        var currentTenantId = context.Principal.FindFirst(config.AzureAdTenantId).Value;
                        if (currentTenantId == "9188040d-6c67-4c5b-b112-36a304b66dad")
                        {
                            // MSA (Microsoft Account) is used to log in
                        }

                        context.HandleCodeRedemption(result.AccessToken, result.IdToken);
                    },
                    // If your application needs to do authenticate single users, add your user validation below.
                    //OnTokenValidated = context =>
                    //{
                    //    return myUserValidationLogic(context.Ticket.Principal);
                    //}
                };
            }

Upvotes: 6

Views: 8484

Answers (2)

Rossco
Rossco

Reputation: 1122

I have been experiencing similar issues with events not seeming to be called. Unfortunately without seeing more code. I presume that the method you've supplied is inside a class implementing IConfigureNamedOptions<OpenIdConnectOptions>.

I'm guessing that you've also proved with breakpoints or logging that the method is actually invoked.

The other thing to try, is to stop assigning a new OpenIdConnectEvents instance to options.Events (i.e. options.Events = new OpenIdConnectEvents). The constructor for OpenIdConnectOptions assigns a value to the Events property, so by reassigning a new instance to this property, you throw away any event hooks that other configuration code might have added. A lot of the example code I have found online does assign a new instance like you have, but it seems like it could create issues to me. Alternatively add your event code to the existing instance e.g.

options.Events.OnAuthorizationCodeReceived = async (context) =>
{
    var code = context.ProtocolMessage.Code;
    var identifier = context.Principal.FindFirst(config.AzureAdObjectIdentifierType).Value;
    var memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
    var graphScopes = _azureOptions.GraphScopes.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

    var cca = new ConfidentialClientApplication(
        _azureOptions.ClientId,
        _azureOptions.BaseUrl + _azureOptions.CallbackPath,
        new ClientCredential(_azureOptions.ClientSecret),
        new SessionTokenCache(identifier, memoryCache).GetCacheInstance(),
        null);
    var result = await cca.AcquireTokenByAuthorizationCodeAsync(code, graphScopes);

    // Check whether the login is from the MSA tenant. 
    // The sample uses this attribute to disable UI buttons for unsupported operations when the user is logged in with an MSA account.
    var currentTenantId = context.Principal.FindFirst(config.AzureAdTenantId).Value;
    if (currentTenantId == "9188040d-6c67-4c5b-b112-36a304b66dad")
    {
        // MSA (Microsoft Account) is used to log in
    }

    context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}

Of course, this still means that you might overwrite some other handler (i.e. options.Events.OnAuthorizationCodeReceived may have a handler already). So you could capture it first and await the result e.g.

var existingHandler = options.Events.OnAuthorizationCodeReceived;
options.Events.OnAuthorizationCodeReceived = async (context) =>
{    
    await existingHandler(context);

    // Your code here...
}

Lastly, if you are using the AzureAdAuthenticationBuilderExtensions.AddAzureAd method in your Startup.ConfigureServices method, then check that the ConfigureAzureOptions.Configure method doesn't assign a new OpenIdConnectEvents as per the above discussion.

Upvotes: 3

daymansiege
daymansiege

Reputation: 137

You should use hybrid mode and set response type to "code id_token", which is "id_token" by default: options.ResponseType = OpenIdConnectResponseType.CodeIdToken

Upvotes: 9

Related Questions