Learn ASP.NET Core 3.1 with mini project : MVC, Razor Pages, Web API, Entity Framework Core, and Blazor. Hands-on online training courses. Click here to know more.

Integrate IdentityServer with ASP.NET Core (Part 4 - User Claims)

In Part 1 of this series we configured a few pieces about user such as name, email, address, and phone in the Server project. But in Part 2 and Part 3 we didn't use them in any way. Now it's time to do that. In this part of the series we will access user's claims first in the Web API and then in the MVC application. We will also involve these claims in the authorization process by creating a custom policy.

The user claims related configuration is scattered at various places in the Server project including IdentityResources property, ApiResources property, Clients property, and TestUsers property. First, we will try to access user claims in the Employees Web API.

Open the EmployeesController from IdentityServerDemo.WebApi project and modify the Get() action as shown below:

[HttpGet]
public List<string> Get()
{
    var givenName=User.Claims.FirstOrDefault
(c => c.Type == "given_name").Value;
    var familyName = User.Claims.FirstOrDefault
(c => c.Type == "family_name").Value;
    var email = User.Claims.FirstOrDefault
(c => c.Type == "email").Value;
    var phone = User.Claims.FirstOrDefault
c => c.Type == "phone").Value;
    var address = User.Claims.FirstOrDefault
(c => c.Type == "address").Value;

    return new List<string>() { 
        givenName,
        familyName,
        email, 
        address,
        phone,
        "Nancy Davolio",
        "Andrew Fuller",
        "Janet Leverling"
    };
}

Notice the code marked in bold letters. It uses user's Claims collection and grabs several claims such as given_name, family_name, email, phone, and address. For the sake of testing this user information is added to the employee list and returned to the caller.

To see the modified Get() in action, run the IdentityServerDemo.Client project and run the Index() action of WebApiClientController we created in Part 2.

If all goes well you will see this in the browser:

If you use user1 account to access the Employees Web API, you will see user1's given_name, family_name, email, address, and phone outputted in addition to the employee names.

If you remember, our Employees controller is decorated with [Authorize] attribute like this:

[ApiController]
[Route("[controller]")]
[Authorize(Roles ="Admin")]
public class EmployeesController : ControllerBase
{
}

We are using role based security here so that only users belonging to Admin role can access the Web API. Now suppose that we want to fine-tune the authorization. We want to allow access to Admin users whose address claim contains only certain country, say USA and UK.

We can accomplish this by creating a custom policy and apply this policy in the [Authorize] attribute.

To create such a policy, open the Startup class of IdentityServerDemo.WebApi project and add the following code:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddAuthorization(o => 
o.AddPolicy("AdminCountryPolicy", p =>
    {
      p.RequireAssertion(ctx =>
      {
        return (ctx.User.IsInRole("Admin") 
               &&( ctx.User.HasClaim("address", "USA") 
               || ctx.User.HasClaim("address", "UK")));
         });
    }
    ));
    ...
    ...
}

Here, we use AddAuthorization() and AddPolicy() to define a new policy called AdminCountryPolicy. The policy requires that a user must belong to Admin role and has address claim with a value of USA or UK. This checking is done using RequireAssertion() method.

After creating AdminCountryPolicy policy you can apply it like this:

[ApiController]
[Route("[controller]")]
[Authorize(Policy = "AdminCountryPolicy")]
public class EmployeesController : ControllerBase
{
  ...
}

This time we used Policy property of [Authorize] attribute and set it to AdminCountryPolicy.

After making this change run the WebApiClientController again using user2 credentials (user2 has role set to Operator. So, it won't fulfill the policy). You will get this error:

Change the role of user2 from Operator to Admin (you can do that in ServerConfiguration class from IdentityServerDemo.Server project) and run the application again. This time you will get this result indicating that our AdminCountryPolicy is working as expected :

You can also test the policy using user1 credentials (user1 is Admin and its address is USA).

Now that we accessed user claims in the Web API project, let's do the same in the MVC client application.

Open Startup class of the IdentityServerDemo.Client project and add the following lines:

.AddOpenIdConnect("oidc", o =>
{
    o.ClientId = "client2";
    o.ClientSecret = "client2_secret_code";
    o.SignInScheme = "Cookies";
    o.Authority = "http://localhost:5000";
    o.RequireHttpsMetadata = false;
    o.ResponseType = "code id_token";
    o.SaveTokens = true;
    o.GetClaimsFromUserInfoEndpoint = true;
    o.Scope.Add("employeesWebApi");
    o.Scope.Add("roles");
    o.Scope.Add("email");
    o.Scope.Add("phone");
    o.Scope.Add("address");
    o.ClaimActions.MapUniqueJsonKey("role", "role");
    o.ClaimActions.MapUniqueJsonKey("email", "email");
    o.ClaimActions.MapUniqueJsonKey("phone", "phone");
    o.ClaimActions.MapUniqueJsonKey("address", "address");
    ...
}

As you can see, we added email, phone, and address scopes and also mapped their JSON keys.

Since the MVC client application has user interface defined in Index.cshtml we will access user claims inside the view rather than controller. So, open Index.cshtml and modify it as shown below:

@model List<string>

<h2>
    Welcome
    @User.Claims.FirstOrDefault(c => 
c.Type == "given_name").Value
    @User.Claims.FirstOrDefault(c => 
c.Type == "family_name").Value!
</h2>


@if (User.IsInRole("Admin"))
{
    <h2>You are Admin</h2>
}

@if (User.IsInRole("Operator"))
{
    <h2>You are Operator</h2>
}

@if (User.HasClaim("address", "USA"))
{
    <h2>Content for admins in USA</h2>
}

@if (User.HasClaim("address", "UK"))
{
    <h2>Content for admins in UK</h2>
}

@if (User.HasClaim(c => c.Type == "email"))
{
    <h3><a href='mailto:@User.Claims
.First(c=>c.Type=="email").Value'>
Send Email</a></h3>
}

<br />

<h3>
    <ul>
        @foreach (var item in Model)
        {
            <li>@item</li>
        }
    </ul>
</h3>


@if (User.Identity.IsAuthenticated)
{
    <h3>
        <a asp-controller="Account" 
asp-action="SignOut">Sign Out</a>
    </h3>
}

Notice the code marked in bold letters. First, we display a welcome message by extracting the value of given_name and family_name claims. Then we check the address claim and display some content depending on user's country. Finally, we render a mailto link using the email claim.

To test this code, run the client application. You will notice that the login process now asks consent to use email, phone, and address. This is because we added those scopes in the client application.

 

Once you allow the access, you should see this result :

In the Web API project you also created a custom policy called AdminCountryPolicy. You can add that policy in the MVC client application also. To do so, open the Startup class of the MVC project and call AddAuthorization() as shown below:

services.AddAuthorization(o => 
o.AddPolicy("AdminCountryPolicy", p =>
{
    p.RequireAssertion(ctx =>
    {
        return (ctx.User.IsInRole("Admin") 
&& (ctx.User.HasClaim("address", "USA") 
|| ctx.User.HasClaim("address", "UK")));
    });
}));

It's essentially the same code that you added in the Employees Web API. After defining the AdminCountryPolicy change the [Authorize] attribute to apply that policy.

[Authorize(Policy = "AdminCountryPolicy")]
public class HomeController : Controller
{
   ...
}

Run the application again and try signing in using different user accounts. 

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, yoga mentor, and meditation teacher. He has been programming, meditating, and teaching for 24+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 24 August 2020


Tags : ASP.NET ASP.NET Core MVC C# Visual Studio


Subscribe to our newsletter

Get monthly email updates about new articles, tutorials, code samples, and how-tos getting added to our knowledge base.

  

Receive Weekly Updates