Search code examples
asp.net-coreauthentication.net-coreidentityserver4

NetCore 3.1 Use IdentityServer4 with existing database and Users table


I have an existing database with a table called Users (its actually an old DotNetNuke 8.1 (like around 2016) database table structure with Users, UserRoles, etc tables). It does not follow the structure of the current Microsoft Identity such as (AspNetUsers, AspNetUserRoles...etc).

I want to add an authentication layer into the NetCore 3.1 project that uses this database. I managed to scaffold the database tables into models and add a db context classes, so I can access the Users table.

How can I add IdentityServer4 for Authentication with the usernames and their passwprds from the Users table.This is what I have so far:

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

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
              
            services.AddDbContext<ApplicationDbContext>(option => option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

            // added for identity server
            services.AddIdentityServer()
                .AddInMemoryApiResources(Configurations.Configuration.GetApis())
                .AddInMemoryClients(Configurations.Configuration.GetClients())
                .AddDeveloperSigningCredential();

            services.AddTransient<IResourceOwnerPasswordValidator, Configurations.ResourceOwnerPasswordValidator>();
            services.AddTransient<IProfileService, Configurations.ProfileService>();

            services.AddControllersWithViews();

          
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ApplicationDbContext dbContext)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            // added for identity server
            app.UseIdentityServer();

            //added
            app.UseAuthentication();

            app.UseAuthorization();

            //added
            dbContext.Database.EnsureCreated();

            app.UseEndpoints(endpoints =>
            {

                endpoints.MapControllerRoute(
                   name: "default",
                   pattern: "{controller=Test}/{action=Index}/{id?}");
            });
        }
    }

The Configuration class for IdentityServer4:

public static class Configuration
    {
        public static IEnumerable<ApiResource> GetApis() =>
            new List<ApiResource> 
            { 
                new ApiResource("ApiOne")  // registering one api with this name
            };

        public static IEnumerable<Client> GetClients() =>    // define a client that will consume above api
            new List<Client>
            {
                new Client
                {
                    ClientId = "resClient",
                    ClientSecrets = { new Secret("topsecret".ToSha256()) },   // make client secret more complex for production, can be made to expire


                    AllowedGrantTypes = GrantTypes.ClientCredentials,    // how client will request the access token

                    //define what the access token will be allowed to be used for, the scopes
                    AllowedScopes = { "ApiOne" }     // this client will be allowed to access Api One
                }
            };


    }

The ProfileService class:

public class ProfileService : IProfileService
    {
        public Task GetProfileDataAsync(ProfileDataRequestContext context)
        {
            context.IssuedClaims = context.Subject.Claims.ToList();
            return Task.FromResult(0);
        }

        public Task IsActiveAsync(IsActiveContext context)
        {
            return Task.FromResult(0);
        }
    }

I try with a username and pass from the db (not hashed, its just for testing) but the return is always:

"unauthorized_client"

enter image description here


Solution

  • This is an example of the config file you need to get IdentityServer4 working in ASP.NET Core 2.2 and above. Create a static class and wire it up in UR startup class like you have done

    public static class InMemoryConfiguration
         {
              public static IEnumerable<ApiResource> ApiResources()
              {
                   return new[] {
                        new ApiResource("yourapp", "Your App", new List<string>() {"role"})
                        {
                             ApiSecrets = { new Secret("topsecret".Sha256()) },
                             UserClaims = { JwtClaimTypes.Email, JwtClaimTypes.Role, JwtClaimTypes.Name, JwtClaimTypes.FamilyName, JwtClaimTypes.GivenName }
                        }
                   };
              }
    
          public static IEnumerable<IdentityResource> GetIdentityResources()
          {
               return new List<IdentityResource>
               {
                   new IdentityResources.OpenId(),
                   new IdentityResources.Profile(),
                   new IdentityResources.Email(),
                   new IdentityResource("roles", "Your App Roles", new List<string>() {"role"})
               };
          }
    
          public static IEnumerable<Client> Clients()
          {
               return new[] {
                    new Client
                    {
                         ClientId = "resClient",
                         ClientSecrets = new [] { new Secret("topsecret".Sha256()) },
                         AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                         AllowedScopes = new [] { "yourapp" },
                         AllowedCorsOrigins = new [] {"http://localhost:8841", "http://localhost:8842"}
                    },
                    new Client
                    {
                         ClientId = "mykabapp_native",
                         ClientSecrets = new [] { new Secret("mykabsecret".Sha256()) },
                         AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                         AllowedScopes = {
                              StandardScopes.OpenId,
                              StandardScopes.Profile,
                              StandardScopes.Email,
                              "mykabapp",
                              "roles"
                          },
                         AllowedCorsOrigins = new [] {"http://localhost:8841", "http://localhost:8842"}
                    }
            };
          }
    
          public static IEnumerable<TestUser> Users()
          {
               return new[] {
                    new TestUser
                    {
                        SubjectId = "012345",
                        Username = "[email protected]",
                        Password = "password"
                    },
                    new TestUser
                    {
                        SubjectId = "123456",
                        Username = "[email protected]",
                        Password = "password"
                    }
               };
          }
     }
     
    

    // AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,

    Pay attention to the line above. its really important and even if your client ID is correct on the client side. You can get the "unauthorized_client" error