Chapter 2 - jayharris/workshop-oidc GitHub Wiki

Chapter 2: The First Client

2.1: Create the Connecting Client

# From ./
dotnet new console --name ConsoleClient --output ./src/ConsoleClient/
dotnet sln add ./src/ConsoleClient/ConsoleClient.csproj

2.2: Install IdentityModel NuGet Packages

# From ./src/ConsoleClient
dotnet add package IdentityModel

2.3: Make Main Method Asynchronous

Add the necessary Using statements. ConsoleClient\Program.cs

using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;

Add an async Main method ConsoleClient\Program.cs replace contents of Program class with:

public static void Main(string[] args)
{
  Console.Title = "Identity Server Console Client";

  MainAsync().GetAwaiter().GetResult();

  Console.Write("\nPress any key to continue... ");
  Console.ReadKey();
}

private static async Task MainAsync()
{
}

2.4: Add a constant for the Provider URL

ConsoleClient\Program.cs within the Program class:

private const string Authority = "http://localhost:5000";

2.5: Send a discovery request to the provider

ConsoleClient\Program.cs within the MainAsync method of the Program class:

var client = new HttpClient();
var discoveryResponse = await client.GetDiscoveryDocumentAsync(Authority);
if (discoveryResponse.IsError) throw new ApplicationException(discoveryResponse.Error);
Console.WriteLine("Successful endpoint discovery");

2.6: Run the Provider and test the Console

# From ./src/IdentityProvider/
dotnet run
# From ./src/ConsoleClient
dotnet run

# Expected result: Exception: "Keyset is missing"

2.7: Add Signing Credentials to the Provider

In IdentityProvider\Startup.cs, modify the method signature of the constructor to add an IHostingEnvironment variable and use that to initialize a class property.

public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
  Configuration = configuration;
  Environment = environment;
}

public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }

In IdentityProvider\Startup.cs, append to ConfigureServices:

if (Environment.IsDevelopment())
{
  identityServerBuilder
    .AddDeveloperSigningCredential();
}
else
{
  throw new Exception("Add Production signing credentials");
}

2.8: Run the Provider and test the Console

# From ./src/IdentityProvider/
dotnet run
# From ./src/ConsoleClient
dotnet run

# Expected result: "Successful endpoint discovery"

2.9: Add a Client configuration to the Provider

IdentityProvider add IdentityConfiguration.cs

using System.Collections.Generic;
using System.Linq;
using IdentityServer4.Models;

namespace IdentityProvider
{
  public static class IdentityConfiguration
  {
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
      return Enumerable.Empty<IdentityResource>();
    }

    public static IEnumerable<ApiResource> GetApis()
    {
      return Enumerable.Empty<ApiResource>();
    } 
    public static IEnumerable<Client> GetClients()
    {
      return Enumerable.Empty<Client>();
    }
  }
}

Update IdentityProvider\Startup.cs within the ConfigureServices method:

// .AddInMemoryIdentityResources(Enumerable.Empty<IdentityResource>())
.AddInMemoryIdentityResources(IdentityConfiguration.GetIdentityResources())
//.AddInMemoryApiResources(Enumerable.Empty<ApiResource>())
.AddInMemoryApiResources(IdentityConfiguration.GetApis())
//.AddInMemoryClients(Enumerable.Empty<Client>())
.AddInMemoryClients(IdentityConfiguration.GetClients())

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

return new List<Client>
{
  new Client
  {
    ClientId = "ConsoleClient",
    ClientName = "Identity Server Console Client",
    ClientSecrets =
    {
        new Secret("secretKey".Sha256())
    },
    AllowedGrantTypes = GrantTypes.ClientCredentials,
    AllowedScopes = { "protectedApi" }
  }
};

Add an API resource to IdentityProfier\IdentityConfiguration.cs within the GetApis method:

return new List<ApiResource>
{
  new ApiResource("protectedApi", "Sample API")
};

2.10: Add constants for the Client configuration

ConsoleClient\Program.cs within the Program class:

private const string ClientId = "ConsoleClient";
private const string ClientSecret = "secretKey";
private const string ClientScope = "protectedApi";

2.11: Send a Token request to the provider

ConsoleClient\Program.cs append to the MainAsync method of the Program class:

// request token
var tokenRequest = new ClientCredentialsTokenRequest {
  Address = discoveryResponse.TokenEndpoint,
  ClientId = ClientId,
  ClientSecret = ClientSecret,
  Scope = ClientScope
};
var tokenResponse = await client.RequestClientCredentialsTokenAsync(tokenRequest);
if (tokenResponse.IsError) throw new ApplicationException(tokenResponse.Error);
Console.WriteLine($"Identity Response Code: {(int) tokenResponse.HttpStatusCode} {tokenResponse.HttpStatusCode}");
Console.WriteLine($"Token Response:\n{tokenResponse.Json}\n\n");

2.12: Run the Provider and test the Console

# From ./src/IdentityProvider/
dotnet run
# From ./src/ConsoleClient
dotnet run

# Expected result: A returned Token Response, a JSON object with an `access_token`
⚠️ **GitHub.com Fallback** ⚠️