'NET Core 3.1 MVC Authorization/Authentication with token (JWT) obtained externally in separate Net Core 3.1 Web Api

I have 3 projects:

  1. Net Core 3.1 MVC project.
  2. Net Core 3.1 Web Api project with JWT auth --> connected to db via Entity Framework
  3. (Xamarin app that also uses the web api for authentication and data retrieval).

I do not want to connect separately to the same db from both projects (1,2) (does not feel like a good idea, correct me if im wrong, would like to keep the db crud operations contained to the web api).

I want to do the authentication in the Web Api project and pass the token to the Net Core MVC project.

I do not know how to use the token to authorize this user for the MVC project, so that controllers can be accessed based on the user's role etc. Basically log this user in in the MVC project with the token obtained in the web api. Is this even possible or a proper method? Any help please?

MVC project Startup.cs class

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

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {            
            // add for razor pages and refresh without rebuilding
            services.AddRazorPages().AddRazorRuntimeCompilation();


            // add httpClient
            services.AddHttpClient();

            // start auth jwt
            services.AddSession(options => {
                options.IdleTimeout = TimeSpan.FromMinutes(1);
            });
            //services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            //Provide a secret key to Encrypt and Decrypt the Token
            var SecretKey = Encoding.ASCII.GetBytes
                 ("mySecretKeyForAuthenticationAndAuthorization");
            //Configure JWT Token Authentication
            services.AddAuthentication(auth =>
            {
                auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(token =>
            {
                token.RequireHttpsMetadata = false;
                token.SaveToken = true;
                token.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
            //Same Secret key will be used while creating the token
            IssuerSigningKey = new SymmetricSecurityKey(SecretKey),
                    ValidateIssuer = true,
            //Usually, this is your application base URL
            ValidIssuer = "https://myAzureWebAPi.azurewebsites.net",
                    ValidateAudience = true,
            //Here, we are creating and using JWT within the same application.
            //In this case, base URL is fine.
            //If the JWT is created using a web service, then this would be the consumer URL.
            ValidAudience = "https://mylocalhost/",
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.Zero
                };
            });
            // end auth jwt


            // add for roles authorization
           services.AddAuthorization(config =>
           {
               config.AddPolicy(Policies.Admin, Policies.AdminPolicy());
               config.AddPolicy(Policies.Client, Policies.ClientPolicy());
           });

            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)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                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();

            //add for jwt
            app.UseCookiePolicy();
            app.UseSession();
            //Add JWToken to all incoming HTTP Request Header
            app.Use(async (context, next) =>
            {
                var JWToken = context.Session.GetString("JWToken");
                if (!string.IsNullOrEmpty(JWToken))
                {
                    context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
                }
                await next();
            });


            app.UseRouting();


            // added for jwt
            app.UseAuthentication();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(    
                     name: "default",
                     pattern: "{area=Client}/{controller=LoginPage}/{action=Index}/{id?}");
            });
        }
    }

MVC LoginPageController.cs

    [Area("Client")]
        public class LoginPageController : Controller
        {
            private readonly IHttpClientFactory _clientFactory;
            public LoginPageController(IHttpClientFactory clientFactory)
            {
                _clientFactory = clientFactory;
            }
      
            public IActionResult Index()
            {
                return View();
            }
    
    
            [HttpPost]
            public async Task<IActionResult> Login([Bind] LoginModel loginModel)
            {
                var client = _clientFactory.CreateClient();
                //var client = new HttpClient();
    
                try
                {
                    var json = JsonConvert.SerializeObject(loginModel);
                    var content = new StringContent(json, Encoding.UTF8, "application/json");
    
                    var outcome = await client.PostAsync("https://<AzureWebAPiUrl>/api/accounts/login", content);
    
                    if (outcome!= null && outcome.IsSuccessStatusCode)
                    {
                        var jsonResult = await outcome.Content.ReadAsStringAsync();
                        var token = JsonConvert.DeserializeObject<Token>(jsonResult);
    
                        Console.WriteLine(token.user_role);
    
                        // store token in a session
                        HttpContext.Session.SetString("JWToken", token.access_token);
    
                        // Here is the problem, once I have the token how do I make this user be 
 //authenticated in the mvc project so that the [Authorize[Role = "someRole"] on controllers works
                    }
    
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
    
                return RedirectToAction("Index", "AdminDeals", new { area = "Admin"}); // only if role is admin
            }
        }


Solution 1:[1]

Hey I have solution for this please refer below point

  1. first of all you need to add authentication. public void ConfigureServices(IServiceCollection services)
           services.AddSession();
           services.AddAuthentication(options =>
           {
                 options.DefaultAuthenticateScheme = >JwtBearerDefaults.AuthenticationScheme;
                 options.DefaultAuthenticateScheme = >JwtBearerDefaults.AuthenticationScheme;
                 options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                 options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
           })
       // Adding Jwt Bearer
       .AddJwtBearer(options =>
       {
           options.SaveToken = true;
           options.RequireHttpsMetadata = false;
           options.TokenValidationParameters = new TokenValidationParameters()
           {
               ValidateIssuer = true,
               ValidateAudience = true,
               ValidAudience = Configuration["JWTConfig:ValidAudience"],
               ValidIssuer = Configuration["JWTConfig:ValidIssuer"],
               IssuerSigningKey = new >SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWTConfig:Secret"]))
           };
       });
  1. After that you have to Use Session for storing authentication token and in this token you have to encrypt token combination of role list whatever role want to pass for the authorization. Here i have used JWT Bearer token
  2. Using this session you have to configure in public void Configure(IApplicationBuilder app, IWebHostEnvironment env)startup.cs file for use header authentication.
   app.UseSession();
   app.Use(async (context, next) =>
   {
      var token = context.Session.GetString("Token");
      if (!string.IsNullOrEmpty(token))
      {
          context.Request.Headers.Add("Authorization", "Bearer " + token);
      }
      await next();
   });
  1. then after you you have to add in your controller
   [Authorize(Roles = "Employee,Student")]
   public ActionResult Leave()
   {
         // your code here
   }

Solution 2:[2]

This is a typical scenario that you should implement OAuth2.0 for your application- You have 2 kinds of clients (MVC and Xamrin), both of them need to be authenticated then access your API project, your API should be protected by a identity provider instead of doing the authenticate by itself. In asp.net core, the most popular solution is Identity Server 4, you don't have to reinvent the wheel , just create an Identity Provider sever, and config your API and MVC project based on the instruction in their documents and everything is ok then. Meanwhile, Identity Server 4 support entity framework

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rahul Bavaliya
Solution 2 Elendil Zheng-MSFT