locked
.Net core Create User API how to access data in UI on multiple pages, Claims say null after page change, JWT RRS feed

  • Question

  • User-544325736 posted

    Hello all,

    I have a API that is CRUD and Login, etc. I also created an MVC .Net Core UI that is the front end of the API. When I go to create a user it calls the API and is inserted into the db. This works fine. The app then goes to a user dashboard once the user signs in. This is where the problem lies, I created claims for the users main data. When I click the profile page The only way I was able to transfer data was with a tempdata string. I tried to put an object in a viewbag or viewdata and every time I go to the controller action it says that object is null. I ended up storing that user object in a json string in tempdata. Is there a better way to do this?

    When I login I create user claims and on the profile page I tried to access the httpconext and all the claims were null.

    ]//login
    public async Task<IActionResult> Login(string username, string password)
            {
                //var result = await _userService.Login(username, password);
                var result = await _authService.Login(loginVM.Username, loginVM.Password);
    
                if (result != null)
                {
                    return Ok(result);
                }
                return BadRequest();
            }
    public string GenerateJWTToken(UserRoleVM user)
            {
                if (user != null)
                {
                    var secretKey = _config.GetValue<string>("Tokens:Key");
                    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
                    var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
                    var currentTime = DateTime.Now;
                    var claims = new Claim[]
                    {
                         new Claim(JwtRegisteredClaimNames.Sub, user.UserName)
                        ,new Claim(JwtRegisteredClaimNames.Sid, user.UserGUID)
                        ,new Claim(JwtRegisteredClaimNames.Email, user.Email)
                        ,new Claim(JwtRegisteredClaimNames.AuthTime, currentTime.ToString())
                        ,new Claim(ClaimTypes.Role, user.Role)
                        //,new Claim(JwtRegisteredClaimNames.)
                        ,new Claim(type: "Subscribed", value: user.IsSubscribed.ToString())
                        ,new Claim(type: "SubscribedDate", value: user.DateSubscribed.ToString())
    
                    };
    
                    var jwtSecurityToken = new JwtSecurityToken
                        (
                            signingCredentials: signingCredentials,
                            audience: _config.GetValue<string>("Tokens:Audience"),
                            issuer: _config.GetValue<string>("Tokens:Issuer"),
                            expires: currentTime.AddSeconds(_config.GetValue<int>("Tokens:LifeTime")),
                            //notBefore: currentTime,
                            claims: claims
                        );
                    return new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
                }
                return null;
            }
    
            //public Task<LoginResponseVM> Login(string username, string password)
            public async Task<string> Login(string username, string password)
            {
                string jwtToken = null;
                if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
                {
                    try
                    {
                        var result = await _signInManager.PasswordSignInAsync(username, password, false, false);
                        if (result.Succeeded)
                        {
                            //var savedUser = await _userService.GetUserByNameAsync(username);
                            //var role = await _userService.GetUserRolesAsync(savedUser);
                            var userVM = await _userService.GetUserNRole2Async(username);
                            jwtToken = GenerateJWTToken(userVM);
    
    
                        }
                    }
                    catch (Exception ex)
                    {
    
                    }
                }
                return jwtToken;
            }
    
    //login UI
            [HttpPost]
            public async Task<IActionResult> Login(LoginVM loginVM)
            {
                if (ModelState.IsValid)
                {
                    AuthUserVM authUserVM = null;
                    using (var client = _httpClient.CreateClient("UserAPI"))
                    {
                        try
                        {
                            using var httpResponse = await client.PostAsync($"/api/Users/Login/",
                                        new StringContent(JsonSerializer.Serialize(loginVM), Encoding.UTF8, "application/json"));
                            if (httpResponse.IsSuccessStatusCode)
                            {                          
                                using var responseStream = await httpResponse.Content.ReadAsStreamAsync();
                                string token = await JsonSerializer.DeserializeAsync<string>(responseStream);
                                var role = UserHelper.GetClaim(token, ClaimTypes.Role);
                                var username = UserHelper.GetClaim(token, "sub");
                                var email = UserHelper.GetClaim(token, "email");
                                var guid = UserHelper.GetClaim(token, "sid");
                                var createdTime = UserHelper.GetClaim(token, "auth_time");
                                var subscribed = UserHelper.GetClaim(token, "Subscribed");
                                var subscribedDate = UserHelper.GetClaim(token, "SubscribedDate");
                                authUserVM = new AuthUserVM
                                {
                                    UserGUID = guid,
                                    Username = username,
                                    Email = email,
                                    Role = role,
                                    Token = token,
                                    IsSubscribed = Convert.ToBoolean(subscribed),
                                    Subscribed = subscribedDate
                                };
                                ViewBag.Account = authUserVM as AuthUserVM;
                                //TempData["Account"] = authUserVM;
                                ViewData["Account"] = authUserVM as AuthUserVM;
                                ViewBag.Accountjson = JsonSerializer.Serialize(authUserVM);
                                TempData["test"] = "test string";
                                TempData["AccountJson"] = JsonSerializer.Serialize(authUserVM);
    
                                List<Claim> lstClaims = new List<Claim>();
                                lstClaims.Add(new Claim(ClaimTypes.Role, role));
                                lstClaims.Add(new Claim("sub", username));
                                lstClaims.Add(new Claim("sid", guid));
                                lstClaims.Add(new Claim("token", token));
                                #region Single Claims
                                //var roleClaim = new Claim(ClaimTypes.Role, role);
                                //var usernameClaim = new Claim("sub", username);
                                //var guidClaim = new Claim("sid", guid);
                                //var tokenClaim = new Claim("token", token); 
                                #endregion
    
                                var claimsIdentity = new ClaimsIdentity(lstClaims, CookieAuthenticationDefaults.AuthenticationScheme);
                                var authProperties = new AuthenticationProperties
                                {
                                    //AllowRefresh = <bool>,
                                    // Refreshing the authentication session should be allowed.
    
                                    //ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
    
                                    // The time at which the authentication ticket expires. A 
                                    // value set here overrides the ExpireTimeSpan option of 
                                    // CookieAuthenticationOptions set with AddCookie.
    
                                    IsPersistent = true,
                                    // Whether the authentication session is persisted across 
                                    // multiple requests. When used with cookies, controls
                                    // whether the cookie's lifetime is absolute (matching the
                                    // lifetime of the authentication ticket) or session-based.
    
                                    //IssuedUtc = <DateTimeOffset>,
                                    // The time at which the authentication ticket was issued.
    
                                    //RedirectUri = <string>
                                    // The full path or absolute URI to be used as an http 
                                    // redirect response value.
                                };
    
                                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                                        new ClaimsPrincipal(claimsIdentity), authProperties);
    
                                TempData["Success"] = "Login Successful.";
                                _logger.LogInformation(DateTime.Now + " - " + loginVM.Username + " Login Success ");
    
                                return RedirectToAction("Dashboard");
                            }
                            else
                            {
                                //httpResponse = await client.PostAsync($"/api/Users/CreateUserAsync/",
                                                        // new StringContent(model, Encoding.UTF8, "application/json")))
                            }
                        }
                        catch (Exception ex) 
                        { 
                            
                        }
                    }
                }
    
                return View(loginVM);            
            }
    
    // Get User UI
    [HttpGet]
            public async Task<IActionResult> ReadUser()
            {
                //var userguid = _httpContext.HttpContext.User.Claims.First(x => x.Type == "sid")?.Value;
                //if (userguid != null) {            //}
                if (ViewBag.Account != null)
                {
                    UserVM model = new UserVM
                    {
                        UserGuid = ViewBag.Account.UserGUID,
                    };
                }
                if (TempData["Account"] != null) {            }
    
                if (TempData["AccountJson"] != null)
                {
                    try
                    {
                        _logger.LogInformation("AccountJson");
                        string json = TempData["AccountJson"].ToString();
                        //var user = JsonSerializer.Parse<AuthUserVM>(json);
    
                        using (JsonDocument document = JsonDocument.Parse(json))
                        {
                            JsonElement element = document.RootElement;
                            string guid = element.GetProperty("UserGUID").GetString();
    
                            if (!string.IsNullOrWhiteSpace(guid))
                            {
                                using (var client = _httpClient.CreateClient("UserAPI"))
                                {
                                    //using var httpResponse = await client.GetAsync($"/api/Users/GetUserByGUID/{guid}");
                                    using var httpResponse = await client.GetAsync($"/api/Users/GetUserByGUID?guid={guid}");
    
                                    if (httpResponse.IsSuccessStatusCode)
                                    {
                                        var responseString = await httpResponse.Content.ReadAsStringAsync();
                                        var user = JsonSerializer.Deserialize<UserVM>(responseString);
                                        ViewData["Account"] = user;
                                        return View(user);
                                    }
                                }
                            }
                        }
                    }
                    catch(Exception ex)
                    {                }
                }
                return View();
            }

    Monday, April 19, 2021 2:54 PM

Answers

  • User475983607 posted

    You have to specify the authorization scheme because you've configured more than one authorization scheme and did not set a default.  It's not real clear why your UI project has more than one scheme.

    As far as I can tell you have not followed the advice given on this forum.  Rather than go back and forth I created a very simple role security example using defaults found in the openly published documentation; https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0

    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.AddControllersWithViews();
    
                services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                    .AddCookie();
    
            }
    
            // 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();
    
                app.UseRouting();
    
                app.UseAuthentication();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{controller=Home}/{action=Index}/{id?}");
                });
            }
        }

    The Account controller handles login, access denied, and role based security.  Nothing fancy.  Noticed the commented code.  If the user's role is set to "User" then the Access Denied page is shown.  Otherwise; the user's claims are displayed.

    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using MvcCookieAuth.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    
    namespace MvcCookieAuth.Controllers
    {
        public class AccountController : Controller
        {
            [HttpGet]
            [Authorize(Roles = "Administrator")]
            public IActionResult Index()
            {
                return View();
            }
            [HttpGet]
            public IActionResult Login()
            {
                return View();
            }
    
            [HttpPost]
            public async Task<IActionResult> LoginAsync(LoginViewModel user)
            {
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.Username),
                    new Claim(ClaimTypes.Role, "Administrator"),
                    //new Claim(ClaimTypes.Role, "User"),
                };
    
                var claimsIdentity = new ClaimsIdentity(
                    claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
                var authProperties = new AuthenticationProperties();
    
                await HttpContext.SignInAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme,
                    new ClaimsPrincipal(claimsIdentity),
                    authProperties);
    
                return RedirectToAction("index");
            }
    
            [HttpGet]
            public IActionResult AccessDenied()
            {
                return View();
            }
        }
    }
    

    Secured Index View

    @{
        ViewData["Title"] = "Index";
    }
    
    <h1>Index</h1>
    
    <div>
        <dl>
            @foreach (var claim in ((System.Security.Claims.ClaimsIdentity)User.Identity).Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>
    </div>

    Access denied view

    @{
        ViewData["Title"] = "AccessDenied";
    }
    
    <h1>Access Denied</h1>
    <div>
        <dl>
            @foreach (var claim in ((System.Security.Claims.ClaimsIdentity)User.Identity).Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>
    </div>

    All you have to do is properly configure the authentication cookie, set the claims, and signin.  Very Very simple.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, May 3, 2021 10:31 PM

All replies

  • User475983607 posted

    It appears you are building a standard MVC application and not an SPA.  Your UI application should generate an authentication cookie after a successful authentication with the Web API.  This lets you use the standard MVC [Authorize] attribute in the MVC application.  The authentication cookie is independent of the Web API security.

    https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0

    Your Web API application security is not well defined.  I assume you intend the client to pass a bearer token to the Web API to gain access to secured resources.  You did not share the configuration so it is unknown at this time. 

    Assuming you intent to use a bearer token, the client must persist the token.  This can be accomplished using any of the standard MVC state management features; Cookie, DB, Session, etc.  If you plan to share the Web API application with other clients then you should learn OAuth/OIDC rather than building a custom solution that other clients might no understand.

    Monday, April 19, 2021 3:29 PM
  • User-544325736 posted

    You are correct, I plan on using a bearer token with the API.

    I create a cookie in the UI after successful authentication, but when it goes to the next page the httpcontext claims are null. in my cookie that i created in the previous page. I thought this would be the easiest way to pass a userid through different pages but its null.

    Any idea why this is?

    I do plan on upgrading to identity4 in the future after i get this running for now.

     services.AddAuthentication(opts =>
                {
                    opts.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                    .AddCookie(cookie =>
                    {
                        cookie.LoginPath = new PathString("/User/Login"); //"https://localhost:50111/users/login";
                        cookie.AccessDeniedPath = new PathString("/Home/About");
                        cookie.SlidingExpiration = true;
                        cookie.Cookie.Name = "HarkinsCookie";
                        // Cookie Security
                        cookie.Cookie.HttpOnly = false; //flag that says cookie only avail to servers,  browser cannot access with JS.
                        cookie.Cookie.SecurePolicy = CookieSecurePolicy.None; // cookie always uses HTTPS, recom: always in prod, none in dev.
                        //cookie.Cookie.SameSite = SameSiteMode.None; // cookie cross-site requests, for OAuth set to Lax, Strict for single site, none does not set header val.
                        cookie.ExpireTimeSpan = TimeSpan.FromHours(2);
                    })
                    .AddJwtBearer(jwt =>
                    {
                        jwt.RequireHttpsMetadata = false;
                        jwt.SaveToken = true;
                        jwt.TokenValidationParameters = new TokenValidationParameters()
                        {
                            ValidIssuer = _configuration.GetSection("Tokens:Issuer").ToString(),
                            ValidAudience = _configuration.GetSection("Tokens:Audience").ToString(),
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Tokens:Key").ToString())),
                            //ValidateLifetime = true,
                            //RequireExpirationTime
                        };
                    });

    I am going to assume:

    This does not generate a auth cookie?

    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                                        new ClaimsPrincipal(claimsIdentity), authProperties);
    Monday, April 19, 2021 3:43 PM
  • User475983607 posted

    I looked at your recent threads and this question has been answered with source code.  The example used a standard POCO to return the JWT and user claims. 

    https://forums.asp.net/t/2173298.aspx

    It seems your new approach return the claim within the JWT and then reads the claims from the JWT.  I assume there is a problem with your helper methods that parse the JWT.  You did not share this code but running your code through the debugger should be enough to find the bugs.

    I recommend removing the try...catch blocks.  All your catches are empty which has a negative benefit.   

    I do plan on upgrading to identity4 in the future after i get this running for now.

    Then just go with IdentityServer4 rather than creating a custom solution that does not follow standards.  You'll end up redoing the code. 

    Monday, April 19, 2021 6:40 PM
  • User-474980206 posted

    there are several issues with you cookie config also:

    cookie.Cookie.HttpOnly = false; 

    as the cookie value is encrypted, not sure why you would expose to the client

    cookie.SlidingExpiration = true;

    as the token expires, sliding is of no use unless you are also using refresh tokens. it should expiration when the token expires. the expired token will require login into the server again.

    cookie.ExpireTimeSpan = TimeSpan.FromHours(2);

    should match the api token

    note: you are using a non-standard flow. typically the UI would redirect to the oauth server for the auth token. before every api call the auth/refresh token would be used to get an access token.  

    if the client js wanted a jwt access token, then typically it would call the oauth server directly, though if the UI server was caching the token, you could have a protected api call.

    you should state what authentication scenarios you are trying to support, and show the released code, rather than showing code, and us having to guess what it is trying to do. 

    Monday, April 19, 2021 10:23 PM
  • User-544325736 posted

    Heyguys,

    I appreciate all of the input and answers you guys give me! they help a lot!

    sorry for the late reply.

    I am working on some final touches with this project before it goes into production.

    I currently have everything with API and UI talking correctly and passing data back and forth.

    Here is one thing I am confused about tho.

    When I login on the UI it goes to the API and checks credentials and sends back a model with the main data to the UI. I store this in a session so I can use it later on. I try to add it to my httpcontext.user.identity and claims but they are always null, even after I signing and add them to it.

    My login on the UI is this:

    [HttpPost]
            public async Task<IActionResult> Login(LoginVM loginVM)
            {
                if (ModelState.IsValid)
                {
                    AuthUserVM authUserVM = null;
                    using (var client = _httpClient.CreateClient("UserAPI"))
                    {
                        try
                        {
                            using var httpResponse = await client.PostAsync($"/api/Users/Login/",
                                        new StringContent(JsonSerializer.Serialize(loginVM), Encoding.UTF8, "application/json"));
                            if (httpResponse.IsSuccessStatusCode)
                            {                          
                                using var responseStream = await httpResponse.Content.ReadAsStreamAsync();
                                string token = await JsonSerializer.DeserializeAsync<string>(responseStream);
                                var role = UserHelper.GetClaim(token, ClaimTypes.Role);
                                var username = UserHelper.GetClaim(token, "sub");
                                var email = UserHelper.GetClaim(token, "email");
                                var guid = UserHelper.GetClaim(token, "sid");
                                var createdTime = UserHelper.GetClaim(token, "auth_time");
                                var subscribed = UserHelper.GetClaim(token, "Subscribed");
                                var subscribedDate = UserHelper.GetClaim(token, "SubscribedDate");
                                authUserVM = new AuthUserVM
                                {
                                    UserGUID = guid,
                                    Username = username,
                                    Email = email,
                                    Role = role,
                                    Token = token,
                                    IsSubscribed = Convert.ToBoolean(subscribed),
                                    Subscribed = subscribedDate
                                };
                                //ViewBag.Account = authUserVM as AuthUserVM;
                                ////TempData["Account"] = authUserVM;
                                //ViewData["Account"] = authUserVM as AuthUserVM;
                                //ViewBag.Accountjson = JsonSerializer.Serialize(authUserVM);
                                //TempData["test"] = "test string";
                                //TempData["AccountJson"] = JsonSerializer.Serialize(authUserVM);
    
                                HttpContext.Session.SetString("sGUID", guid);
                                HttpContext.Session.SetString("sAccount", JsonSerializer.Serialize(authUserVM));
                                HttpContext.Session.SetString("sRole", role);
                                //HttpContext.Session.SetObjectAsJson()
                                //var xz = HttpContext.Session.GetObjectFromJson<UserVM>("x");
    
                                List<Claim> lstClaims = new List<Claim>();
                                lstClaims.Add(new Claim(ClaimTypes.Role, role));
                                lstClaims.Add(new Claim("sub", username));
                                lstClaims.Add(new Claim("sid", guid));
                                lstClaims.Add(new Claim("token", token));
                                #region Single Claims
                                //var roleClaim = new Claim(ClaimTypes.Role, role);
                                //var usernameClaim = new Claim("sub", username);
                                //var guidClaim = new Claim("sid", guid);
                                //var tokenClaim = new Claim("token", token); 
                                #endregion
    
                                var claimsIdentity = new ClaimsIdentity(lstClaims, CookieAuthenticationDefaults.AuthenticationScheme);
                                var authProperties = new AuthenticationProperties
                                {
                                    //AllowRefresh = <bool>,
                                    // Refreshing the authentication session should be allowed.
    
                                    //ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
    
                                    // The time at which the authentication ticket expires. A 
                                    // value set here overrides the ExpireTimeSpan option of 
                                    // CookieAuthenticationOptions set with AddCookie.
    
                                    IsPersistent = false,
                                    // Whether the authentication session is persisted across 
                                    // multiple requests. When used with cookies, controls
                                    // whether the cookie's lifetime is absolute (matching the
                                    // lifetime of the authentication ticket) or session-based.
    
                                    //IssuedUtc = <DateTimeOffset>,
                                    // The time at which the authentication ticket was issued.
    
                                    //RedirectUri = <string>
                                    // The full path or absolute URI to be used as an http 
                                    // redirect response value.
                                };
    
                                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                                        new ClaimsPrincipal(claimsIdentity), authProperties);
    
                                //HttpContext.User = new GenericPrincipal(new ClaimsPrincipal(username.ToString()), role.ToArray());
                                GenericIdentity genericIdentity = new GenericIdentity(authUserVM.Username, ClaimTypes.NameIdentifier);
                                string[] roles = { role };
                                GenericPrincipal genericPrincipal = new GenericPrincipal(claimsIdentity, roles);
                                IPrincipal identity = (IPrincipal)genericPrincipal;
                                HttpContext.User = (ClaimsPrincipal)identity;
    
                                TempData["Success"] = "Login Successful.";
                                ViewData["Success"] = "Login Successful.2";
                                _logger.LogInformation(DateTime.Now + " - " + loginVM.Username + " Login Success ");
    
                                return RedirectToAction("Dashboard");
                            }
                            else
                            {
                                TempData["Error"] = "Invalid Username or Password.\n Please try again.";
                            }
                        }
                        catch (Exception ex) 
                        { 
                            
                        }
                    }
                }
    
                return View(loginVM);            
            }

    When I go and check httpcontext in debug I find all 3 sessions. but the User part is always null.

    When I go to the admin controller I want to 

    [Authorize(Roles = "Administrator")]

    but this I'm not sure about since my 'User' in my httpcontext is null.

    The only way I got Authorize to work was with:

            [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
    

    but this does not check the 'Role' it only makes sure the user is logged in.

    I am currently not using my JWT token until my next API I create. This current microservice is 'User' and it's just checking Roles of users for dashboards.

    I would gladly clear any other questions up if you have any. 

    How would I be able to use Authorize on my Role with my setup? I have a API and UI 2 separate .net core projects. I'm using httpclient to talk between the 2.

    Monday, May 3, 2021 8:54 PM
  • User475983607 posted

    You have to specify the authorization scheme because you've configured more than one authorization scheme and did not set a default.  It's not real clear why your UI project has more than one scheme.

    As far as I can tell you have not followed the advice given on this forum.  Rather than go back and forth I created a very simple role security example using defaults found in the openly published documentation; https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0

    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.AddControllersWithViews();
    
                services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                    .AddCookie();
    
            }
    
            // 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();
    
                app.UseRouting();
    
                app.UseAuthentication();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{controller=Home}/{action=Index}/{id?}");
                });
            }
        }

    The Account controller handles login, access denied, and role based security.  Nothing fancy.  Noticed the commented code.  If the user's role is set to "User" then the Access Denied page is shown.  Otherwise; the user's claims are displayed.

    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Authentication.Cookies;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using MvcCookieAuth.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    
    namespace MvcCookieAuth.Controllers
    {
        public class AccountController : Controller
        {
            [HttpGet]
            [Authorize(Roles = "Administrator")]
            public IActionResult Index()
            {
                return View();
            }
            [HttpGet]
            public IActionResult Login()
            {
                return View();
            }
    
            [HttpPost]
            public async Task<IActionResult> LoginAsync(LoginViewModel user)
            {
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.Username),
                    new Claim(ClaimTypes.Role, "Administrator"),
                    //new Claim(ClaimTypes.Role, "User"),
                };
    
                var claimsIdentity = new ClaimsIdentity(
                    claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
                var authProperties = new AuthenticationProperties();
    
                await HttpContext.SignInAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme,
                    new ClaimsPrincipal(claimsIdentity),
                    authProperties);
    
                return RedirectToAction("index");
            }
    
            [HttpGet]
            public IActionResult AccessDenied()
            {
                return View();
            }
        }
    }
    

    Secured Index View

    @{
        ViewData["Title"] = "Index";
    }
    
    <h1>Index</h1>
    
    <div>
        <dl>
            @foreach (var claim in ((System.Security.Claims.ClaimsIdentity)User.Identity).Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>
    </div>

    Access denied view

    @{
        ViewData["Title"] = "AccessDenied";
    }
    
    <h1>Access Denied</h1>
    <div>
        <dl>
            @foreach (var claim in ((System.Security.Claims.ClaimsIdentity)User.Identity).Claims)
            {
                <dt>@claim.Type</dt>
                <dd>@claim.Value</dd>
            }
        </dl>
    </div>

    All you have to do is properly configure the authentication cookie, set the claims, and signin.  Very Very simple.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Monday, May 3, 2021 10:31 PM
  • User-544325736 posted

    Thank you,

    Here is my startup, I believe that is the authorization scheme?

     services.AddAuthentication(opts =>
                {
                    opts.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                    opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                    opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                    .AddCookie(cookie =>
                    {
                        cookie.LoginPath = new PathString("/User/Login"); //"https://localhost:50111/users/login";
                        cookie.AccessDeniedPath = new PathString("/Home/About");
                        cookie.SlidingExpiration = true;
                        cookie.Cookie.Name = "HarkinsCookie";
                        // Cookie Security
                        cookie.Cookie.HttpOnly = false; //flag that says cookie only avail to servers,  browser cannot access with JS.
                        cookie.Cookie.SecurePolicy = CookieSecurePolicy.None; // cookie always uses HTTPS, recom: always in prod, none in dev.
                        //cookie.Cookie.SameSite = SameSiteMode.None; // cookie cross-site requests, for OAuth set to Lax, Strict for single site, none does not set header val.
                        cookie.ExpireTimeSpan = TimeSpan.FromHours(2);
                    })
                    .AddJwtBearer(jwt =>
                    {
                        jwt.RequireHttpsMetadata = false;
                        jwt.SaveToken = true;
                        jwt.TokenValidationParameters = new TokenValidationParameters()
                        {
                            ValidIssuer = _configuration.GetSection("Tokens:Issuer").ToString(),
                            ValidAudience = _configuration.GetSection("Tokens:Audience").ToString(),
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Tokens:Key").ToString())),
                            //ValidateLifetime = true,
                            //RequireExpirationTime
                        };
                    });

    For my project I have the cookiescheme to hold the user data and I wanted it to hold the claims as well so I can use Roles in the UI.

    I have the JWTscheme so I can have multiple APIs and verify that the user can use the API.

    I have a login that creates a claim and and signs in. I thought that was all it was to it like you said. I am probably missing something simple. When I try to use the Authorization (role=user) my 'user' of httpcontext is null. also all the claims in the identity are not seen.

    Tuesday, May 4, 2021 1:26 AM
  • User-544325736 posted

    You solved my problem!

    it was because my default scheme was jwt and not cookie

    It works now!

    Tuesday, May 4, 2021 1:51 AM