Setup an SPA PKCE client by code on the IdServer, missing documentation (I suppose it is just this)
Using simpleidserver 6, I am trying to implement a dedicated Id server for an SPA application using PKCE.
This SPA authenticates with no pb if it is setup for other Id servers like Azure EntraId or Keycloack. So it should not be an issue at client side. For information the client lib is oidc-client-ts.
The whole authentication process seems to work smoothly: it redirects to the Id server that accepted the authentication request. The user can fill in his authentication information that is accepted, and he is redirected to the source application with a success status.
The issue come when the client application request the final access_token against the /token endpoint (once again, I don't do this manually, as I let oidc-client-ts do the whole job for me and works veryu well with other IdServers).
The request to get the token seems fine:
Till version 4.x.x, TokenEndPointAuthMethod supported 'pkce' as there was an implementation of IOAuthClientAuthenticationHandler that had AuthMethod with this value. Since v5.x.x, I don't see anywhere in your source code that mention of pkce, so I'm confused as it is explicitly mentionned on the documentation of v6.x.x that SPA with PKCE is supported. Could anybody tell me how to setup the server by code to permit an SPA client application to authenticate using PKCE?
Here is my Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using SimpleIdServer.IdServer.Builders;
using SimpleIdServer.IdServer.Config;
using SimpleIdServer.IdServer.Domains;
using System.Collections.Generic;
var users = new List<User>
{
UserBuilder.Create("administrator", "password", "Administrator").SetEmail("[email protected]").SetFirstname("Administrator").Build()
};
var clients = new List<Client>
{
CreateClient("fp-client", "FP",["https://localhost:44417"],
[
ScopeBuilder.CreateApiScope("api://fp.backend/request", true) .Build(),
ScopeBuilder.CreateApiScope( "email", true).Build(),
ScopeBuilder.CreateApiScope( "profile", true).Build()
]),
// THIS DOESN'T WORK WITH AS IT REQUIRES A SECRET, WHICH IS NOT COMPLIANT WITH
// PKCE STANDARDS AND DOESN'T MAKE SENSE FOR A PUBLIC CLIENT
// BUT I SUPPOSE "TRADITIONAL WEBSITE CLIENT" MEANS "TRADITIONAL SERVER-SIDE
// WEBSITE CLIENT"
// ClientBuilder
// .BuildTraditionalWebsiteClient("fp-client", "secret", null, "https://localhost:44417")
// .AddScope([
// ScopeBuilder.CreateApiScope("api://fp.backend/request", true) .Build(),
// ScopeBuilder.CreateApiScope( "email", true).Build(),
// ScopeBuilder.CreateApiScope( "profile", true).Build()
// ])
// .DisableConsent()
// .Build(),
};
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()));
builder.AddSidIdentityServer()
.AddInMemoryClients(clients)
.AddDeveloperSigningCredential()
.AddInMemoryUsers(users)
.AddInMemoryLanguages(DefaultLanguages.All)
.AddPwdAuthentication(true);
var app = builder.Build();
app.UseCors();
app.Services.SeedData();
app.UseSid();
app.Run();
Client CreateClient(string clientId, string clientName, string[] redirectUrls, Scope[] scopes)
{
var client = ClientBuilder
.BuildTraditionalWebsiteClient(clientId, "secret", null, redirectUrls)
.AddScope(scopes)
.DisableConsent()
.SetClientName(clientName)
.Build();
// I CLEAR SECRETS, HOPING THE SECRETS WON'T BE CHECKED BUT STILL, VALIDATION SEEMS TO FAIL
client.Secrets.Clear();
// HERE, I TRIED EVERYTHING THAT WE CAN SEE IN ANY IMPLEMENTATION
// OF `IOAuthClientAuthenticationHandler.AuthMethod`. NONE OF THEM WORKS EITHER
// client.TokenEndPointAuthMethod = OAuthClientSecretPostAuthenticationHandler.AUTH_METHOD;
//EVEN WITHOUT THIS IT FAILS
client.GrantTypes = ["authorization_code"];
//EVEN WITHOUT THIS IT FAILS
client.ResponseTypes = ["code"];
return client;
}
Hello,
Because your application is an SPA, the client must be created using the BuildUserAgentClient function. By default, the client created with BuildTraditionalWebsiteClient is not public, and PKCE will not be used.
Could you either use the BuildUserAgentClient function or set the IsPublic property to true?
Kind regards, SID