Chapter 6 - jayharris/workshop-oidc GitHub Wiki

Chapter 6: Web Client

6.1: Create the Web Client

# From ./
dotnet new mvc --name WebClient --output ./src/WebClient/ --auth None
dotnet sln add ./src/WebClient/WebClient.csproj

6.2: Turn off HTTPS

Do not do this for production. Because this is a workshop about Identity Server and not SSL configuration, we will disable HTTPS and leave that for another workshop.

WebClient\Startup.cs within the Configure method

//app.UseHttpsRedirection();

6.3: Change the HTTP and HTTPS port numbers

The default template sets the HTTPS and HTTP ports to 5001 and 5000, respectively, but these are already in use by the Identity Provider. Change them to 5021 and 5020.

WebClient/Properties/launchSettings.json within the Profiles.WebClient values settings.

"WebClient": {
  "commandName": "Project",
  "launchBrowser": true,
  "applicationUrl": "https://localhost:5021;http://localhost:5020",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
}

6.4: Run the Site.

Before proceeding on to Identity Server configuration, make sure the site works as expected.

# From: ./src/WebClient/
dotnet run

Load the site in a browser, accessing it at http://localhost:5020/. A successful request will return the site's Welcome page.

Stop execution after you have completed testing.

6.5: Add a new Client configuration to the Identity Provider

Add a client to IdentityProfier\IdentityConfiguration.cs within the GetClients method:

new Client
{
  ClientId = "WebClient",
  ClientName = "Identity Resource Web Client",
  ClientSecrets =
  {
      new Secret("secretKey".Sha256())
  },
  RequireConsent = false,
  // where to redirect to after login
  RedirectUris = { "http://localhost:5020/signin-oidc" },
  PostLogoutRedirectUris = { "https://localhost:5020/signout-callback-oidc" },
  AllowedGrantTypes = GrantTypes.Implicit,
  AllowedScopes = { "openid","profile" }
}

6.6: Configure Middleware and Services

WebClient\Startup.cs

using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Authentication.Cookies;

Remove existing JWT Mappings

WebClient/Startup.cs append to ConfigureServices method:

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

WebClient/Startup.cs append to ConfigureServices method:

services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        //options.SignInScheme = "Cookies";
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;

        options.ClientId = "WebClient";
        options.ClientSecret = "secretKey";
        options.SaveTokens = true;

        //options.Scope.Clear();
        //foreach (var scope in openIdConfig.Scope.Split(' '))
        //{
        //    options.Scope.Add(scope);
        //}
    });

Enable Authentication middleware.

WebClient/Startup.cs within Configure method, add app.UseAuthentication() above the Static Files middleware configuration:

//app.UseHttpsRedirection();
app.UseAuthentication();
app.UseStaticFiles();

6.7: To assist testing, add a new action to the Home controller

WebClient/Controllers/HomeController.cs within the HomeController class:

public IActionResult Claims()
{
    return View();
}

Add WebClient/Views/Home/Claims.cshtml

@{
    ViewData["Title"] = "Claims";
}
<h2>@ViewData["Title"]</h2>

<p>The current user's claims:</p>

@if (!User.Claims.Any())
{
    <p><i>None</i></p>
}
<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

Add a link to the navigation bar.

WebClient/Views/Shared/_Layout.cshtml

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Claims">Claims</a>
</li>
<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>

6.8: Add Authorize attribute to Claims action

The Values controller was generated by default within the project template.

WebClient\Controllers\HomeController.cs

using Microsoft.AspNetCore.Authorization;

WebClient\Controllers\HomeController.cs adding an attribute to the Claims method.

[Authorize]
public IActionResult Claims()

6.9: Run the Site. Access the generated controller.

# From ./src/IdentityProvider/
dotnet run
# From: ./src/WebClient/
dotnet run

Load the site in a browser, accessing its Claims url at http://localhost:5020/Home/Claims. A successful request will redirect to the Provider for login, and display User claims after login.

6.10: Implement Logout

Add a Logout Link to the Navigation bar. WebClient/Views/Shared/_Layout.cshtml

@if (User.Identity.IsAuthenticated)
{
  <ul class="nav navbar-nav">
    <li class="nav-item">@Html.ActionLink("Log out", "Logout", "Home") @User.Identity.Name</li>
  </ul>
}
<ul class="navbar-nav flex-grow-1">
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="CLaims">Claims</a>
    </li>
    <li class="nav-item">
        <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
    </li>
</ul>

Add a Logout Action. WebClient\Controllers\HomeController.cs

using Microsoft.AspNetCore.Authentication;

WebClient\Controller\HomeController.cs

public async Task Logout()
{
  await HttpContext.SignOutAsync("Cookies");
  await HttpContext.SignOutAsync("oidc");
}

6.11: Run the Site.

# From ./src/IdentityProvider/
dotnet run
# From: ./src/WebClient/
dotnet run

Load the site in a browser, accessing its Claims url at http://localhost:5020/Home/Claims. After accessing the page, logout. A successful request will redirect to the Logged Out page of the Provider. NOTE: You will still be logged in to the provider, as indicated by the username / logout link on the Provider. Upon returning to the home page of the WebClient at http://localhost:5020/, there is no logout link.

6.12: Fix logout for the Provider template.

The issue is that the default UI for an AspNetCore template requires that logout be done through a POST request, which blocks OIDC logouts.

Generate AspNetCore Identity Logout files to modify functionality.

# From: ./src/IdentityProvider
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet aspnet-codegenerator identity --files Account.Logout --dbContext IdentityProvider.Data.ApplicationDbContext

Replace the Logout action. IdentityProvider/Areas/Identity/Pages/Account/Logout.cshtml.cs replace the OnGet method.

public async Task<IActionResult> OnGet()
{
    await _signInManager.SignOutAsync();
    ViewData["IsLoggedOut"] = true;
    _logger.LogInformation("User logged out.");
    return Page();
}

Modify _LoginPartial IdentityProvider\Views\Shared\_LoginPartial.cshtml

@if (!(true.Equals(ViewData["IsLoggedOut"])) && SignInManager.IsSignedIn(User))

6.13: Run the Site.

# From ./src/IdentityProvider/
dotnet run
# From: ./src/WebClient/
dotnet run

Load the site in a browser, accessing its Claims url at http://localhost:5020/Home/Claims. After accessing the page, logout. A successful request will redirect to the unauthenticated Logged Out page of the Provider.

⚠️ **GitHub.com Fallback** ⚠️