Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JWT Bearer Token not working with ASP.Net Core 3.1 + Identity Server 4

I'm currently trying to create a web api for an mobile app im developing. I decided on using ASP.Net Core 3.1 with the included Indentity Server to use OpenID Connect + PKCE for Authentication. The User will authorize the app via WebView and the access token will be used for subsequent calls to the api. The Controllers of the api are decorated with the [Authorize] Attribute.

I tried to use some of the scaffolded code for authentication but right now I struggling since the jwt token doesn't seem to get recognized under some conditions.

In the scaffolded code, the ConfigureServices method in the Startup Class called services.AddDefaultIdentity<ApplicationUser>(). The problem with that was, that this also added all of the default endpoints for Identity which I didnt need/want (/Account/Manage/Disable2fa, /Account/Manage/PersonalData etc.). Therefore I changed to code to services.AddIdentity<ApplicationUser, IdentityRole>(), because I read multiple times nowthat AddDefaultIdentity() apparently does the same as AddIdentity() but also calls .AddDefaultUI()

After testing the login flow, it turns out that the api controllers no longer accept the Authorization: Bearer ... Header after making the change and api calls always get redirected to the login page as if the user is not logged in

When I change the code back to AddDefaultIdentity, the JWT Bearer Token is correctly authenticated again and I can access the controller with the [Authorize] Attribute but then i got the problem again with the default Identity Pages ...

My Question is: How do I secure my API Controllers with the [Authorize] Arribute and JWT Bearer Header without having to include the default Identity UI?

Startup.cs

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Munchify.Web.Data;
using Munchify.Web.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using APIBackend.Web.Services;

namespace APIBackend.Web
{
    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>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));

            services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>()
                .AddDefaultTokenProviders();

            services.ConfigureApplicationCookie(options =>
            {
                options.LoginPath = "/Identity/Account/Login";
            });

            services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

            services.AddAuthentication()
                .AddIdentityServerJwt();

            services.AddTransient<IEmailSender, EmailSender>();

            services.AddControllersWithViews();
            services.AddRazorPages();

        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }
            else
            {
                app.UseExceptionHandler("/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.UseIdentityServer();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }
    }
}
like image 976
Henrik Storck Avatar asked Jun 29 '20 14:06

Henrik Storck


People also ask

Can I use JWT as bearer token?

JSON Web Token (JWT, RFC 7519) is a way to encode claims in a JSON document that is then signed. JWTs can be used as OAuth 2.0 Bearer Tokens to encode all relevant parts of an access token into the access token itself instead of having to store them in a database.

What is the difference between JWT and bearer token?

In essence, a JSON Web Token (JWT) is a bearer token. It's a particular implementation which has been specified and standardised. JWT in particular uses cryptography to encode a timestamp and some other parameters. This way, you can check if it's valid by just decrypting it, without hitting a DB.

What is AddIdentityServerJwt?

builder.Services.AddAuthentication() .AddIdentityServerJwt(); The authentication middleware that is responsible for validating the request credentials and setting the user on the request context: C# Copy.


1 Answers

I managed to fix it 🥳

I basically just replaced

services.AddIdentity<ApplicationUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

with

services.AddAuthentication(o =>
{
    o.DefaultScheme = IdentityConstants.ApplicationScheme;
    o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(o => { });

var identityService = services.AddIdentityCore<ApplicationUser>(o =>
{
    o.Stores.MaxLengthForKeys = 128;
    o.SignIn.RequireConfirmedAccount = true;
})
    .AddDefaultTokenProviders()
    .AddEntityFrameworkStores<ApplicationDbContext>();

identityService.AddSignInManager();
identityService.Services.TryAddTransient<IEmailSender, EmailSender>();

because that's what AddDefaultIdentity() does behind the scenes without the UI part.

Now my JWT Header Authentication works again

Hope this helps someone with the same issue :)

like image 66
Henrik Storck Avatar answered Oct 09 '22 04:10

Henrik Storck