1

I have a application where the user is being logged in using the Azure AD with the details then stored in the database. Now if the user exists in the database they are allocated a role. This role is added to the claims in the startup.cs, which is all fine. However when a user is not stored in the database and needs to be created. The issue is then how do I manage to update the cookie with the user role from the UserController.cs

This is my startup.cs ConfigurationService()

        services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
            .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));

        services.AddControllersWithViews(options =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        });
        services.AddRazorPages()
             .AddMicrosoftIdentityUI();

For users that are stored in the database, this is where the role is added to the claims

        app.Use((context, next) =>
        {
            var userId = context.User.Identity.Name;
            if (userId == null)
            {
                return next();
            }

            var userService = context.RequestServices.GetRequiredService<IUserService>();

            if (!userService.DoesUserExist())
                return next();

            var roles = userService.GetUser().Role.Type;

            if (!string.IsNullOrEmpty(roles))
            {
                context.User.Identities.FirstOrDefault().AddClaim(new Claim(ClaimTypes.Role, roles));
            }

            return next();
        });

The application endpoint is

    [HttpGet]
    public IActionResult SignInRequest()
    {
        if (_userService.DoesUserExist())
        {
            return RedirectToAction(nameof(Index), "Bookings");
        }

        return RedirectToAction(nameof(Create));
    }

For users that are not currently stored in the database they are displayed with a create view where they are required to complete some additional information before being saved to the database. The View

    [HttpGet]
    public IActionResult Create()
    {
        return View(GetNewUser());
    }

Get New User

private UserVM GetNewUser()
    {
        return new UserVM()
        {
            Id = Guid.NewGuid(),
            Firstname = _claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName).Value,
            Lastname = _claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname).Value,
            Oid = _claimsPrincipal.Claims.FirstOrDefault(c => c.Type == "http://schemas.microsoft.com/identity/claims/objectidentifier").Value,
            Roles = _rolesService.GetRoles().Select(s => new SelectListItem(s.Type, s.Id.ToString())).ToList(),
            Locations = _locationService.GetAllLocations().Select(s => new SelectListItem(s.Name, s.Id.ToString())).ToList(),
            SelectedRole = UserRoles.EmployeeOnly
        };
    }

Once the new user has completed the additional information. It is here I would like to update the identity and cookie with the roles claim WITHOUT needing the user to log out and log back in again.

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create(UserVM user)
    {
        //here is where I would like to update the cookie and identity claims
        return RedirectToAction(nameof(Index), "Bookings");
    }

This is my user model for my views

public class UserVM
{
    public Guid Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public int RoleId { get; set; }
    public Guid? LocationId { get; set; }
    public string Oid { get; set; }

    public RolesVM Role { get; set; }

    public LocationVM Location { get; set; }

    [DisplayName("Name")]
    public string FullName 
    {
        get { return $"{Firstname} {Lastname}"; }
        set { FullName = ""; } 
    }

    public List<SelectListItem> Locations { get; set; }
    public List<SelectListItem> Roles { get; set; }

    public string SelectedLocation { get; set; }
    public string SelectedRole { get; set; }
}

There will also be a user edit page where a user could have their roles changed by a higher levelled user. There are four main Roles "Super User, Management, Admin, Employee" all new users will be given the Employee role until it is changed by some higher.

I have tried to look about on google for some solid examples of this happening and have been pointed in the direction of UserManager and RoleManager but have been unable to date to get this to work mainly due to lack of knowledge using any of these before. I have seen some using IClaimTransformation but again I have not been able to get this to work with the only example I have found. I would thankful if someone could help point out the steps that are needs or to point me in the direction of a good example I could use a reference point.

  • Are you making use of app.UseCookieAuthentication(new CookieAuthenticationOptions()); or default authentication scheme as cookie in start up ? – kavyaS Oct 27 '21 at 14:11
  • Hi @kavyasaraboju-MT I am just using the default authentication scheme app.UseAuthentication(); – Steven Shields Oct 27 '21 at 14:14

1 Answers1

0

After much research I came across a method that already exists in the ControllerBase.cs called SignIn(ClaimsPrincipal principal). I had a look at the github repo for MVC and found out that this will go down the stack to call httpContext.SignInAsync(). This was the method I had been looking to attach to in order to allow the cookie to be updated and have updated the required method in my UserController to the following. Side note I have changed the name of the method

    public IActionResult Welcome(UserVM user)
    {
        if (!ModelState.IsValid)
        {
            user.Locations = GetLocations();
            return View(user);
        }

        user.RoleId = Convert.ToInt32(user.SelectedRole);
        user.LocationId = new Guid(user.SelectedLocation);

        if (!_userService.CreateNewUser(_mapper.Map<Users>(user)))
        {
            ModelState.AddModelError("", "Error creating user");
            user.Locations = GetLocations();
            return View(user);
        }

        var result = SignIn(_claimsPrincipal); //THIS WAS ALL THAT WAS NEEDED

        return RedirectToAction(nameof(Index), "Bookings");
    }
Nimantha
  • 6,405
  • 6
  • 28
  • 69