Skip to content

Instantly share code, notes, and snippets.

@pauldougan
Created April 9, 2024 14:19
Show Gist options
  • Save pauldougan/ed37d3d8c2d69eb8dce966ac28ee3c6c to your computer and use it in GitHub Desktop.
Save pauldougan/ed37d3d8c2d69eb8dce966ac28ee3c6c to your computer and use it in GitHub Desktop.
Integrate dotnet core with One Login - example

Installation notes

brew install --cask dotnet-sdk

openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

openssl rsa -pubout -in private_key.pem -out public_key.pem

mkdir dotnet-test

cd dotnet-test

Make an auth only client with self service setting the public key

dotnet new web

replace the Program.cs generated with this copy

replace the ClientId

dotnet run

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.CookiePolicy;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Security.Claims;
namespace govuk_one_login_aspdotnet_core
{
public class Program
{
const string ENVIRONMENT_DOMAIN = "oidc.integration.account.gov.uk";
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
//options.Cookie.SameSite = SameSiteMode.Lax;
//options.Cookie.SecurePolicy = CookieSecurePolicy.None;
//options.Cookie.HttpOnly = true;
})
.AddOpenIdConnect(options =>
{
options.ClientId = "YOUR_CLIENT_ID_FROM_SELF_SERVICE";
options.MetadataAddress = $"https://{ENVIRONMENT_DOMAIN}/.well-known/openid-configuration";
// Scope is a list of information that the client is requesting from the user.
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("email");
options.Scope.Add("phone");
options.UsePkce = false;
options.ResponseMode = "query";
options.ResponseType = "code";
options.SaveTokens = true;
// The UserInfo endpoint is used to get the claims.
options.GetClaimsFromUserInfoEndpoint = true;
// GOV.UK One Login used a client assertian to secure the token exchange instead of a client secret.
// This is a JWT signed with the client's private key.
// The public key is registered with GOV.UK One Login.
options.Events.OnAuthorizationCodeReceived = context =>
{
// Load the private key from a secure location.
// This example loads the private key from a file, but you could use a secret store.
var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText("../private_key.pem"));
var clientPrivateKey = new RsaSecurityKey(rsa);
var signingCredentials = new SigningCredentials(clientPrivateKey, "RS256");
// Create a JWT token with the client ID as the issuer and the token endpoint as the audience.
var tokenHandler = new JwtSecurityTokenHandler();
// var securityToken = tokenHandler.CreateJwtSecurityToken(
// notBefore: DateTime.UtcNow,
// issuer: context.Options.ClientId,
// audience: $"https://{ENVIRONMENT_DOMAIN}/token",
// signingCredentials: signingCredentials
// );
var jwt = new JwtSecurityToken(
issuer: context.Options.ClientId,
audience: $"https://{ENVIRONMENT_DOMAIN}/token",
claims: new List<Claim> { new Claim("jti", "arandomvalue"), new Claim("sub", context.Options.ClientId ) },
expires: DateTime.UtcNow.AddMinutes(5),
signingCredentials: signingCredentials
);
// Set the client assertion on the token request.
var clientAssertion = tokenHandler.WriteToken(jwt);
Console.WriteLine(clientAssertion);
context.TokenEndpointRequest!.ClientAssertion = clientAssertion;
context.TokenEndpointRequest.ClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
return Task.CompletedTask;
};
}
);
var app = builder.Build();
app.UseAuthentication();
if(app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCookiePolicy(new CookiePolicyOptions
{
OnAppendCookie = cookieContext =>
{
cookieContext.CookieOptions.SameSite = SameSiteMode.Lax;
cookieContext.CookieOptions.Secure = false;
},
HttpOnly = HttpOnlyPolicy.Always,
MinimumSameSitePolicy = SameSiteMode.Lax,
Secure = CookieSecurePolicy.None
});
app.MapGet("/", async (ctx) => {
if (ctx.User.Identity!.IsAuthenticated)
{
await ctx.Response.WriteAsync("<html><head><title>Home</title></head><body><a href=\"/logout\">Logout</a></body></html>");
}
else
{
await ctx.Response.WriteAsync("<html><head><title>Home</title></head><body><a href=\"/login\">Login</a></body></html>");
}
});
app.MapGet("/login", async (ctx) => {
if (!ctx.User.Identity!.IsAuthenticated)
{
await ctx.ChallengeAsync();
}
else {
ctx.Response.Redirect("/");
}
});
app.MapGet("/logout", async (ctx) => {
await ctx.SignOutAsync();
ctx.Response.Redirect("/");
});
app.Run();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment