Home > Software engineering >  May i implement Identity Server 4 Authentification with JWT token and MVC Client using IS4 template?
May i implement Identity Server 4 Authentification with JWT token and MVC Client using IS4 template?

Time:11-01

I have a problem with Identity Server. Recently i got a task on an internship to implement the Identity Server 4 authentification, so first of all I decided to connect IS4 with default MVC client. I figured out how to do it with cookies, as they are used by default (turned out it was easy enough if using templates). But then I got task to use JWT tokens. And that's where I have a problem. All tutorials say that I have to rewrite my IS4 project from the very beginning, but is there an opportunity to use the IS4 template?

Identity server startup file using cookies(by default .Net 3.1 is used)

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();

            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;

                // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
                options.EmitStaticAudienceClaim = true;
            })
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryApiScopes(Config.GetApiScopes())
                .AddInMemoryClients(Config.GetClients())
                .AddAspNetIdentity<ApplicationUser>();

            // not recommended for production - you need to store your key material somewhere secure
            builder.AddDeveloperSigningCredential();

            services.AddAuthentication()
                .AddGoogle(options =>
                {
                    options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                    
                    // register your IdentityServer with Google at https://console.developers.google.com
                    // enable the Google  API
                    // set the redirect URI to https://localhost:5001/signin-google
                    options.ClientId = "copy client ID from Google here";
                    options.ClientSecret = "copy client secret from Google here";
                });
        }

        public void Configure(IApplicationBuilder app)
        {
            if (Environment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }

            app.UseStaticFiles();

            app.UseRouting();
            app.UseIdentityServer();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }

IS Config.cs file

public static IEnumerable<ApiResource> GetApiResources()
        {
            yield return new ApiResource("SwaggerAPI");
            yield return new ApiResource("OrdersAPI");
        }

        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            yield return new IdentityResources.OpenId();
            yield return new IdentityResources.Profile();
        }

        /// <summary>
        /// IdentityServer4 version 4.x.x changes
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiScope> GetApiScopes()
        {
            yield return new ApiScope("SwaggerAPI", "Swagger API");
            yield return new ApiScope("blazor", "Blazor WebAssembly");
            yield return new ApiScope("OrdersAPI", "Orders API");
        }

        public static IEnumerable<Client> GetClients() =>
        new List<Client>
        {
            new Client
            {
                ClientId = "add_mvc",
                ClientSecrets = { new Secret("add_mvc_secret".ToSha256()) },

                AllowedGrantTypes = GrantTypes.Code,

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

                RedirectUris = {"https://localhost:7272/signin-oidc"},
                PostLogoutRedirectUris = {"https://localhost:7272/signout-callback-oidc"},

                RequireConsent = false,

                AccessTokenLifetime = 5,

                AllowOfflineAccess = true

                // AlwaysIncludeUserClaimsInIdToken = true
            }
        };

MVC client program.cs file(.NET 6.0)

builder.Services.AddAuthentication(config =>
{
    config.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    config.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, config =>
    {
        config.Authority = "https://localhost:5001";
        config.ClientId = "add_mvc";
        config.ClientSecret = "add_mvc_secret";
        config.SaveTokens = true;
        config.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateAudience = false
        };

        config.ResponseType = "code";

        config.Scope.Add("OrdersAPI");
        config.Scope.Add("offline_access");

        config.GetClaimsFromUserInfoEndpoint = true;

        config.ClaimActions.MapJsonKey(ClaimTypes.DateOfBirth, ClaimTypes.DateOfBirth);
    });

builder.Services.AddAuthorization(config =>
{
    config.AddPolicy("HasDateOfBirth", builder =>
    {
        builder.RequireClaim(ClaimTypes.DateOfBirth);
    });
});

builder.Services.AddHttpClient();

// Add services to the container.
builder.Services.AddControllersWithViews();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

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

app.Run();

I also tried just to add JWT configuration in IS4 startup.cs file, but the error said that jwt tokens can't by used with some methods from signinmanager.

CodePudding user response:

A best practice is to implement and host IdentityServer, Client and the API in separate services. By separating them, it will be much easier to debug and reason about. The client should receive an access token that it then can use to call an API using the HttpClient library.

You do not need to add AddJwtBearer to your client or IdentityServer, instead you add that to your separate API project. The main purpose of .AddJwtBearer is to verify and convert the incoming access token into ClaimsPrincipal user.

You can use this code in the Client to get access to to the various tokens:

string accessToken = await HttpContext.GetTokenAsync("access_token");

string idToken = await HttpContext.GetTokenAsync("id_token");

string refreshToken = await HttpContext.GetTokenAsync("refresh_token");

string tokenType = await HttpContext.GetTokenAsync("token_type");         

string accessTokenExpire = await HttpContext.GetTokenAsync("expires_at");    

Then you can use the access token to call the API using the HttpClient library.

  • Related