1

I would like to use ASP.NET Core Identity and IdentityServer together and provide Role-based authorization.

3 projects in the solution:

  • ApplicationAPI
  • ApplicationWeb
  • IdentityServer
2
  • What's not working? Nothing, or just RBAC? I can't see your UseIdentityserver middleware anywhere. Commented Feb 11, 2022 at 20:08
  • 1
    @Pieterjan thanks for your comment, you can access to repo here in this repo works fine. Commented Feb 11, 2022 at 20:14

1 Answer 1

1

Statup.cs in API Client

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

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddControllers();
        services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
                    options.Authority = "https://localhost:5001";
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateAudience = false
                    };
                });
        services.AddAuthorization(options =>
        {
            options.AddPolicy("ApiScope", policy =>
            {
                policy.RequireAuthenticatedUser();
                policy.RequireClaim("scope", "api1");
            });
        });
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseHttpsRedirection();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers()
                      .RequireAuthorization("ApiScope");
        });
    }
}

Startup.cs in MVC Client

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

  public void ConfigureServices(IServiceCollection services)
  {
    services.AddControllersWithViews();
    JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
    services.AddAuthentication(options =>
    {
      options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
      options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
    {
      options.Authority = "https://localhost:5001";
      options.ClientId = "mvc";
      options.ClientSecret = "secret";
      options.ResponseType = "code id_token";
      options.Scope.Add("email");
      options.Scope.Add("roles");
      options.ClaimActions.DeleteClaim("sid");
      options.ClaimActions.DeleteClaim("idp");
      options.ClaimActions.DeleteClaim("s_hash");
      options.ClaimActions.DeleteClaim("auth_time");
      options.ClaimActions.MapJsonKey("role", "role");
      options.Scope.Add("api1");
      options.SaveTokens = true;
      options.GetClaimsFromUserInfoEndpoint = true;
      options.TokenValidationParameters = new TokenValidationParameters
      {
        NameClaimType = "name",
        RoleClaimType = "role"
      };
    });
    services.AddTransient<AuthenticationDelegatingHandler>();
    services.AddHttpClient("ApplicationAPI", client =>
    {
      client.BaseAddress = new Uri("https://localhost:5002/");
      client.DefaultRequestHeaders.Clear();
      client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    }).AddHttpMessageHandler<AuthenticationDelegatingHandler>();

    services.AddHttpClient("ApplicationIdentityServer", client =>
    {
      client.BaseAddress = new Uri("https://localhost:5001/");
      client.DefaultRequestHeaders.Clear();
      client.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/json");
    });
    services.AddHttpContextAccessor();
  }
  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  {
    if (env.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
    }
    else
    {
      app.UseExceptionHandler("/Home/Error");
      app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
      endpoints.MapControllerRoute(
                  name: "default",
                  pattern: "{area=Admin}/{controller=Home}/{action=Index}/{id?}");
    });
  }
}

AuthenticationDelegatingHandler in MVC Application

To prevent getting token again.

public class AuthenticationDelegatingHandler : DelegatingHandler
{
  private readonly IHttpContextAccessor _httpContextAccessor;

  public AuthenticationDelegatingHandler(IHttpContextAccessor httpContextAccessor)
  {
    _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
  }

  protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);

    if (!string.IsNullOrWhiteSpace(accessToken))
    {
      request.SetBearerToken(accessToken);
    }

    return await base.SendAsync(request, cancellationToken);
  }
}

Config.cs in IdentityServer

public static class Config
{
  public static IEnumerable<IdentityResource> IdentityResources =>
      new List<IdentityResource>
      {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResources.Email(),
        new IdentityResource("roles", "Your role(s)", new List<string>() { "role" })
      };
  public static IEnumerable<ApiScope> ApiScopes =>
      new List<ApiScope>
      {
        new ApiScope("api1", "My API")
      };

  public static IEnumerable<Client> Clients =>
    // we can remove API client here because we call API from MVC
    // and pass the token to API Application.
    // Only we get token from IdentityServer with MVC Application 
    new List<Client>
    {
      new Client
      {
          ClientId = "client",
          ClientSecrets = { new Secret("secret".Sha256()) },
          AllowedGrantTypes = GrantTypes.ClientCredentials,
          AllowedScopes = { "api1" }
      },
      new Client
      {
          ClientId = "mvc",
          ClientName = "Application Web",
          AllowedGrantTypes = GrantTypes.Hybrid,
          ClientSecrets = { new Secret("secret".Sha256()) },
          RequirePkce = false,
          AllowRememberConsent = false,
          RedirectUris = { "https://localhost:5003/signin-oidc" },
          PostLogoutRedirectUris = { "https://localhost:5003/signout-callback-oidc" },

          AllowedScopes = new List<string>
          {
              IdentityServerConstants.StandardScopes.OpenId,
              IdentityServerConstants.StandardScopes.Profile,
              IdentityServerConstants.StandardScopes.Email,
              "api1",
              "roles"
          }
      }
    };
}

I added ASP.NET Core Identity in the IdentityServer project

Startup.cs in IdentityServer

public class Startup
{
  public IWebHostEnvironment Environment { get; }
  public IConfiguration Configuration { get; }
  public Startup(IWebHostEnvironment environment, IConfiguration configuration)
  {
    Environment = environment;
    Configuration = configuration;
  }
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddControllersWithViews();
    services.AddRazorPages()
        .AddRazorPagesOptions(options =>
        {
          options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
        });
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
    {
      options.SignIn.RequireConfirmedEmail = true;
    })
        .AddEntityFrameworkStores<ApplicationDbContext>()
        .AddDefaultTokenProviders();

    var builder = services.AddIdentityServer(options =>
    {
      options.Events.RaiseErrorEvents = true;
      options.Events.RaiseInformationEvents = true;
      options.Events.RaiseFailureEvents = true;
      options.Events.RaiseSuccessEvents = true;
      options.EmitStaticAudienceClaim = true;
      options.UserInteraction.LoginUrl = "/Account/Login";
      options.UserInteraction.LogoutUrl = "/Account/Logout";
      options.Authentication = new AuthenticationOptions()
      {
        CookieLifetime = TimeSpan.FromHours(10),
                CookieSlidingExpiration = true
      };
    })
        .AddInMemoryIdentityResources(Config.IdentityResources)
        .AddInMemoryApiScopes(Config.ApiScopes)
        .AddInMemoryClients(Config.Clients)
        .AddAspNetIdentity<ApplicationUser>();

    if (Environment.IsDevelopment())
    {
      builder.AddDeveloperSigningCredential();
    }
    services.AddAuthentication()
        .AddGoogle(options =>
        {
          options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
          options.ClientId = "copy client ID from Google here";
          options.ClientSecret = "copy client secret from Google here";
        });

    services.AddTransient<IEmailSender, EmailSender>();
  }
  public void Configure(IApplicationBuilder app)
  {
    if (Environment.IsDevelopment())
    {
      app.UseDeveloperExceptionPage();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseIdentityServer();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
      endpoints.MapControllerRoute(
                  name: "default",
                  pattern: "{controller=Home}/{action=Index}/{id?}");
      endpoints.MapRazorPages();
    });
  }
}

Nuget Packages I used in IdentityServer project:

    <PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.2" />

    <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="5.0.12" />

    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.12" />

    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="5.0.2" />
    <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />

    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.12" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.12">
        <PrivateAssets>all</PrivateAssets>
        <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>

    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.12" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="5.0.12" />

source code repo

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.