How to Secure your .Net Core API Through Identity Server 4

The identity server is an open-source authentication server with OpenID Connect. That provides a single point to authenticate/authorize the user, so that different systems do not need to implement authentication flow and could communicate with each other too as they share same security channel.

Before starting the Implementation, let’s discuss the little component of Identity Server.

Identity Resource

The Identity resources are the data which we want to secure like userID, Email, Phone number Etc. The Identity resources are unique names and assigned with a claim, this claim is added in the token, User can access the identity resource through scope to that claim.

public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResource( name: "openid", userClaims: new[] { "sub" }, displayName: "Your user identifier") }; }

Scope: We scope are defined for access level of the client to the user claim via resources. We can define the scope of the client and what he can access to the resources.

var webViewer = new Client { ClientId = "web_viewer", AllowedScopes = { "openid", "profile", "read" } };

we are defining scope of the user to the resource OpenID which have user claim. So, it will be the scope to that user claim.

API Resources

When API services increase the scope also increases and the managing the scope becomes complex so you can bundle the scope into the API resources and assign that to Client.

public static IEnumerable<ApiScope> GetApiScopes()
{
    return new List<ApiScope>
    {
        // invoice API specific scopes
        new ApiScope(name: "invoice.read",   displayName: "Reads your invoices."),
        new ApiScope(name: "invoice.pay",    displayName: "Pays your invoices."),
    };
}
public static readonly IEnumerable<ApiResource> GetApiResources()
{
    return new List<ApiResource>
    {
        new ApiResource("invoice", "Invoice API")
        {
            Scopes = { "invoice.read", "invoice.pay", "manage", "enumerate" }
        }
    };
}

Resources Isolation

OAuth does not have API resources and has only scope so as there could be many resources the scope could increase so will the token so for this, we have API Resources. So, to solve this we have resource isolation, requesting token for specific resource.

Client: The client is the application that wants to access the resources. The scope is assigned to the Client.

The client has 5 things.

  1. Client Id, which is unique,
  2. Secret,
  3. Grant Type
  4. Redirect URL where the token will be sent.
  5. List of scope client is allowed to access.

Let’s start by creating a new Web API project.

Now install the NuGet package for identity server

  • IdentityServer4
  • IdentityServer4.EntityFramework
  • Microsoft.AspNetCore.Authentication.OpenIdConnect
  • IdentityServer4.Storage

1. Start by adding the connection string for Database.

"ConnectionStrings": {

    "DefaultConnection": "Data Source=SQL Server Name;Initial Catalog=Database Name;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"

  }

2. Create DB context for our identity.

public class AppDbContext : IdentityDbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
}

3. Register our Identity in the Program.cs file

var defaultConnString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(defaultConnString,
         b => b.MigrationsAssembly(assembly)));

builder.Services.AddIdentity<IdentityUser, IdentityRole>()

    .AddEntityFrameworkStores<AppDbContext>();

4. We need to create tables for our identity so we will create migration and run it.

Add-Migration IdentityMigration -Context AppDbContext

we need to update the database by running the migration.

Update-Database -Context AppDbContext

5. Add identity server 4 to our application.

Add the following code so in the program.cs file.

builder.Services.AddIdentityServer()
    .AddAspNetIdentity<IdentityUser>()
    .AddConfigurationStore(options =>
    {
        options.ConfigureDbContext = b => b.UseSqlServer(defaultConnString, opt => opt.MigrationsAssembly(assembly));
    })
    .AddOperationalStore(options =>
    {
        options.ConfigureDbContext = b => b.UseSqlServer(defaultConnString, opt => opt.MigrationsAssembly(assembly));

    })
    .AddDeveloperSigningCredential();

And following line after Create Build.

  • app.UseIdentityServer();

We need to create migration and run it.

  • add-migration IdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext
  • add-migration IdentityServerConfigurationDbMigration -c ConfigurationDbContext

Now run the Migration.

  • update-database -Context PersistedGrantDbContext
  • update-database -Context ConfigurationDbContext

6. Now we will create the configuration for our identity the client, Scope, and Resources.

We will Implement it in a config.cs file and will add it our database.

public static class Config
 {
     public static IEnumerable<IdentityResource> GetIdentityResources()
     {
         return new List<IdentityResource>
         {
             new IdentityResources.OpenId(),
             new IdentityResources.Profile(),
         };
     }

     public static IEnumerable<ApiScope> ApiScopes =>
        new[] { new ApiScope("api1"), };

     public static IEnumerable<ApiResource> GetApis()
     {
         return new List<ApiResource>
     {
         new ApiResource("api1", "My API")
     };
     }

     public static IEnumerable<Client> GetClients()
     {
         return new List<Client>
     {
         new Client
         {
             ClientId = "client",
             // no interactive user, use the clientid/secret for authentication
             AllowedGrantTypes = GrantTypes.ClientCredentials,
             // secret for authentication
             ClientSecrets =
             {
                 new Secret("secret".Sha256())
             },
             // scopes that client has access to
             AllowedScopes = { "api1" }
         },
         // resource owner password grant client
         new Client
         {
             ClientId = "ro.client",
             AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
             ClientSecrets =
             {
                 new Secret("secret".Sha256())
             },
             AllowedScopes = { "api1" }
         },
         // OpenID Connect hybrid flow client (MVC)
         new Client
         {
             ClientId = "mvc",
             ClientName = "MVC Client",
             AllowedGrantTypes = GrantTypes.Code,
             ClientSecrets =
             {
                 new Secret("secret".Sha256())
             },
             RedirectUris           = { "https://localhost:7088/signin-oidc" },
             PostLogoutRedirectUris = { "https://localhost:7088/signout-callback-oidc" },

             AllowedScopes =
             {
                 IdentityServerConstants.StandardScopes.OpenId,
                 IdentityServerConstants.StandardScopes.Profile,
                 "api1"
             },

             AllowOfflineAccess = true,
             RequirePkce = true,
         }
      };
     }

 }

7. Add this configuration, Add the following code into the program.cs.

#region Database Ready

using (var serviceScope = app.Services.GetService<IServiceScopeFactory>().CreateScope())
{
    serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
    var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
    context.Database.Migrate();
    if (!context.Clients.Any())
    {
        foreach (var client in Config.GetClients())
        {
            var test = client.ToEntity();
            context.Clients.Add(client.ToEntity());
        }
        context.SaveChanges();
    }
    if (!context.IdentityResources.Any())
    {
        foreach (var resource in Config.GetIdentityResources())
        {
            context.IdentityResources.Add(resource.ToEntity());
        }
        context.SaveChanges();
    }

    if (!context.ApiScopes.Any())
    {
        foreach (var resource in Config.ApiScopes.ToList())

        {
            context.ApiScopes.Add(resource.ToEntity());

        }
        context.SaveChanges();
    }
    if (!context.ApiResources.Any())
    {
        foreach (var resource in Config.GetApis())
        {
            context.ApiResources.Add(resource.ToEntity());
        }
        context.SaveChanges();
    }

}

#endregion

Now Run the program and our Identity Server 4 is ready !!


Similar Articles