Sitecore Identity – 2 – Adding web clients

In the first part of the series I tried to explain and give a very small introduction to what is Sitecore Identity and the basic implementation that comes out of the box with Sitecore 9.1 +

In this part I will show some coding and how to build an external web application that uses the Sitecore Identity server to authenticate users, and to connect to the Sitecore instance APIs.

Remember in the first part of this series, I showed that the default implementation comes with a default client named Sitecore, which is the Sitecore instance itself protected by the identity server.

In this code sample I will show 2 things:

  1. Configuring/Adding a Client App in the Sitecore Identity (SI)
  2. Building an App that uses SI as a token issuing authority that protects the Sitecore instance apis and data.

So let’s get started,

Configuring a Client

Sitecore Identity (SI) has the clients configured in the file <instance>.identityserver\sitecore\Sitecore.Plugin.IdentityServer\Config\identityServer.xml here we will add a new client with some default properties.

<WebAppClient>
   <ClientId>WebAppClient</ClientId>
   <ClientName>Web App Client</ClientName>
   <AccessTokenType>0</AccessTokenType>
   <AllowOfflineAccess>true</AllowOfflineAccess>
   <AlwaysIncludeUserClaimsInIdToken>false</AlwaysIncludeUserClaimsInIdToken>
   <AccessTokenLifetimeInSeconds>3600</AccessTokenLifetimeInSeconds>
   <IdentityTokenLifetimeInSeconds>3600</IdentityTokenLifetimeInSeconds>
   <AllowAccessTokensViaBrowser>true</AllowAccessTokensViaBrowser>
   <RequireConsent>false</RequireConsent>
   <RequireClientSecret>false</RequireClientSecret>
   <AllowedGrantTypes>
      <AllowedGrantType1>hybrid</AllowedGrantType1>
      <AllowedGrantType2>client_credentials</AllowedGrantType2>
   </AllowedGrantTypes>
   <RedirectUris>
      <RedirectUri1>{AllowedCorsOrigin}/signin-oidc</RedirectUri1>
   </RedirectUris>
   <PostLogoutRedirectUris>
      <PostLogoutRedirectUri1>{AllowedCorsOrigin}/signout-callback-oidc</PostLogoutRedirectUri1>
   </PostLogoutRedirectUris>
   <AllowedCorsOrigins>
      <AllowedCorsOrigin1>https://localhost:44343</AllowedCorsOrigin1>
   </AllowedCorsOrigins>
   <AllowedScopes>
      <AllowedScope1>openid</AllowedScope1>
      <AllowedScope2>sitecore.profile</AllowedScope2>
      <AllowedScope3>sitecore.profile.api</AllowedScope3>
   </AllowedScopes>
   <UpdateAccessTokenClaimsOnRefresh>true</UpdateAccessTokenClaimsOnRefresh>
</WebAppClient>   

Adding the Client Code

Now that we configured our client so the identity server knows about it, we will build the application itself. In this sample I started from a plain ASP.NET Core 2.2 MVC application.
The folks behind the IdentityServer4 also built client tools https://github.com/IdentityModel which make it easy to authenticate against OpenIDC and OAuth2 servers.

For a change I will use the new Razor Page Model, instead of the traditional View Controllers. But first things first, after adding the nuget for IdentityModel we need to register and configure the services we will need in the Startup.cs as shown.

using IdentityModel;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;

namespace WebAppClient
{
    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)
        {
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie(options =>
            {
                options.ExpireTimeSpan = TimeSpan.FromMinutes(60);
                options.Cookie.Name = "mvcimplicit";
            })
            .AddOpenIdConnect("oidc", options =>
            {
                options.ClientId = "WebAppClient";
                options.Authority = "https://sc910.identityserver";
                options.RequireHttpsMetadata = false;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.ResponseType = "code token";

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("sitecore.profile");
                options.Scope.Add("offline_access");
                options.Scope.Add("sitecore.profile.api");

                options.SaveTokens = true;

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = JwtClaimTypes.Name,
                    RoleClaimType = JwtClaimTypes.Role,
                };
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseAuthentication();
            app.UseMvcWithDefaultRoute();
        }
    }
}

There is not much difference between View Controllers and Razor Page Models in this sample, with one exception, that you get the current context using HttpContext instead of the Context property.
I added a Secure Page which renders the values it receives from the Sitecore Identity.

@page
@using Microsoft.AspNetCore.Authentication
@model WebAppClient.Pages.SecureModel
@{
    ViewData["Title"] = "Secure";
}

<h1>Secure Page</h1>

<h2>Claims</h2>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<h2>Properties</h2>

<dl>
    @foreach (var prop in (await HttpContext.AuthenticateAsync()).Properties.Items)
    {
        <dt>@prop.Key</dt>
        <dd>@prop.Value</dd>
    }
</dl>
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebAppClient.Pages
{
    [Authorize]
    public class SecureModel : PageModel
    {
        public void OnGet()
        {

        }
    }
}

References

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.