0

I am doing user authentication in my startup.cs. I need to query my database using the OpenIDConnect claims info. This is what I have done but don't know how to get the connection to work. I tried injecting the db query constructor at the top of the startup.cs like this and then calling the query as follows:

public class Startup
{        
    protected IAdoSqlService _adoSqlService;
    public Startup(IConfiguration configuration, IAdoSqlService adoSqlService)
    {
        Configuration = configuration;
         _adoSqlService = adoSqlService;
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        // do ConfigureServices stuff
        
        options.Events = new OpenIdConnectEvents()
        {
            OnTokenValidated = async ctx =>
            {
                // This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly

                ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
                string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;

                //How do I call the database to run the following query 
                int isUser = _adoSqlService.isUser(userntid);

                if (isUser > 0)
                {
                     claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                }
                else
                {
                    claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
                }
            }
        }
        
        //More stuff
    }
}

When I run the above, it errors in program.cs before even running with the following error
System.InvalidOperationException: 'Unable to resolve service for type 'XXXX.Services.IAdoSqlService' while attempting to activate 'XXXX.Startup'.'

So how do I make the call _adoSqlService.isUser(userntid); to the database?

I am NOT using EF.

Solution

I figured this out by doing the following:

  1. I moved most of my services to the top of the ConfigureServices section (based on something that @qudus said) before I performed my authentication.

  2. I removed the database injection code from the top of the startup.cs.

  3. Lastly I changed the OnTokenValidated to use the following:

    ctx.HttpContext.RequestServices.GetRequiredService();

Here is the code:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    internal static IConfiguration Configuration { get; private set; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        var connectionSection = Configuration.GetSection("ConnectionStrings");
        services.Configure<ConnectionStrings>(connectionSection);
        services.AddScoped<IAdoSqlService, AdoSqlService>();
        services.AddControllersWithViews();
        services.AddHttpContextAccessor();            
        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddSession();

        // Load the Federation configuration section from app settings
        var federationConfig = Configuration.GetSection("Federation");
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie(options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
            options.SlidingExpiration = true;// default
            options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
        })
        .AddOpenIdConnect(options =>
        {
            //Set Options here......

            //optional customizations to the auth and failure events
            options.Events = new OpenIdConnectEvents()
            {
                OnRedirectToIdentityProvider = context =>
                {
                    return Task.CompletedTask;
                },
                OnRemoteFailure = context =>
                {
                    // handle an error response from Federation and redirect the user to a custom error page instead
                    context.Response.Redirect("/Error/401");
                    context.HandleResponse();
                    return Task.CompletedTask;
                },
                OnTokenValidated = async ctx =>
                {
                    // This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
                    ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
                    string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
                    string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;

                    int isUser = 0;
                    int isAdmin = 0;

                    try 
                    { 
                        var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
                        isUser = db.isUser(userntid);
                        isAdmin = db.isAdmin(userntid);
                    }
                    catch (Exception ex)
                    {
                        string error = ex.Message;
                    }

                    AppHttpContext.Current.Session.SetString("IsUser", "false");
                    if (isUser > 0)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                        AppHttpContext.Current.Session.SetString("IsUser", "true");
                    }

                    AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
                    if (isAdmin > 0)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
                        AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
                    }

                    if (isUser == 0 && isAdmin == 0)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
                    }

                }
            };
        });
FlyFish
  • 491
  • 5
  • 22
  • Did you register the _adoSqlService underneath? – Qudus Nov 17 '20 at 21:09
  • Underneath where? I tried to use it the same way I do on the rest of my controllers. Is there anything specific that I would need to do on the startup.cs that is different? – FlyFish Nov 17 '20 at 21:18
  • 1
    I meant - Did you register the service in this `ConfigureServices` method of your startup class like this `services.AddScoped();` if you did, which is likely. You should try move it to the top of the `ConfigureServices` method because it seems you're accessing the service just before it is resolved. – Qudus Nov 17 '20 at 21:23
  • Good suggestion, but it does not appear to work. I moved it to the top of the 'ConfigureServices' section but it threw the same error. – FlyFish Nov 17 '20 at 21:53
  • I'm very much interested in what the cause of the issue is if or when you figure it out. – Qudus Nov 18 '20 at 12:50
  • @Qudus - I figured it out. See my edit above. Thanks for the suggestion to move where things were registered. – FlyFish Nov 18 '20 at 14:51
  • This may be relevant to your situation. https://stackoverflow.com/a/32461831 – Yehuda Makarov Nov 18 '20 at 14:59
  • @FlyFish Glad you did. – Qudus Nov 18 '20 at 16:57

1 Answers1

1

Solution

I figured this out by doing the following:

  1. I moved most of my services to the top of the ConfigureServices section (based on something that @qudus said) before I performed my authentication.

  2. I removed the database injection code from the top of the startup.cs.

  3. Lastly I changed the OnTokenValidated to use the following:

    ctx.HttpContext.RequestServices.GetRequiredService();

Here is the code:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    internal static IConfiguration Configuration { get; private set; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        var connectionSection = Configuration.GetSection("ConnectionStrings");
        services.Configure<ConnectionStrings>(connectionSection);
        services.AddScoped<IAdoSqlService, AdoSqlService>();
        services.AddControllersWithViews();
        services.AddHttpContextAccessor();            
        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddSession();

        // Load the Federation configuration section from app settings
        var federationConfig = Configuration.GetSection("Federation");
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });
        services.AddAuthentication(sharedOptions =>
        {
            sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie(options =>
        {
            options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
            options.SlidingExpiration = true;// default
            options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
        })
        .AddOpenIdConnect(options =>
        {
            //Set Options here......

            //optional customizations to the auth and failure events
            options.Events = new OpenIdConnectEvents()
            {
                OnRedirectToIdentityProvider = context =>
                {
                    return Task.CompletedTask;
                },
                OnRemoteFailure = context =>
                {
                    // handle an error response from Federation and redirect the user to a custom error page instead
                    context.Response.Redirect("/Error/401");
                    context.HandleResponse();
                    return Task.CompletedTask;
                },
                OnTokenValidated = async ctx =>
                {
                    // This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
                    ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
                    string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
                    string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;

                    int isUser = 0;
                    int isAdmin = 0;

                    try 
                    { 
                        var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
                        isUser = db.isUser(userntid);
                        isAdmin = db.isAdmin(userntid);
                    }
                    catch (Exception ex)
                    {
                        string error = ex.Message;
                    }

                    AppHttpContext.Current.Session.SetString("IsUser", "false");
                    if (isUser > 0)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
                        AppHttpContext.Current.Session.SetString("IsUser", "true");
                    }

                    AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
                    if (isAdmin > 0)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
                        AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
                    }

                    if (isUser == 0 && isAdmin == 0)
                    {
                        claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
                    }

                }
            };
        });
FlyFish
  • 491
  • 5
  • 22