An ASP.NET Core authentication handler adding support for authenticating visitors using the IndieAuth protocol.
- .NET 8.0 (LTS)
- .NET 10.0
This library is distributed as a NuGet package. To install with the .NET CLI:
dotnet add package AspNet.Security.IndieAuthConfigure authentication at startup:
var authBuilder = builder.Services.AddAuthentication()
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.LoginPath = "/account/sign-in";
})
.AddIndieAuth(IndieAuthDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.ClientId = config.IndieAuth.ClientId;
options.CallbackPath = "/authentication/indie-auth/callback";
});Trigger authentication by issuing a challenge:
return Challenge(new IndieAuthChallengeProperties
{
Me = domain,
Scope = new[] { "profile", "create" },
RedirectUri = ReturnUrl
}, IndieAuthDefaults.AuthenticationScheme);To protect API endpoints with IndieAuth tokens (using token introspection):
builder.Services.AddAuthentication()
.AddIndieAuthBearer("IndieAuthBearer", options =>
{
// Option 1: Explicit introspection endpoint
options.IntrospectionEndpoint = "https://auth.example.com/introspect";
// Option 2: Discover from authority URL
options.Authority = "https://example.com/";
// Authentication for introspection endpoint
options.IntrospectionAuthenticationMethod = IntrospectionAuthMethod.Bearer;
options.IntrospectionToken = "resource-server-token";
// Or use client credentials
options.IntrospectionAuthenticationMethod = IntrospectionAuthMethod.ClientCredentials;
options.ClientId = "my-resource-server";
options.ClientSecret = "secret";
// Caching (optional)
options.CacheIntrospectionResults = true;
options.IntrospectionCacheExpiration = TimeSpan.FromMinutes(5);
});Use the [Authorize] attribute to protect controllers:
[Authorize(AuthenticationSchemes = "IndieAuthBearer")]
public class MicropubController : Controller
{
public IActionResult Post()
{
var me = User.FindFirst("me")?.Value;
var scopes = User.FindFirst("scope")?.Value?.Split(' ');
// ...
}
}For simplified configuration, use the convenience extension methods:
// Minimal API - IndieAuth login
builder.Services.AddAuthentication()
.AddIndieAuth(
clientId: "https://myapp.example.com/",
callbackPath: "/auth/callback",
signInScheme: CookieAuthenticationDefaults.AuthenticationScheme);
// Minimal API - Bearer token protection
builder.Services.AddAuthentication()
.AddIndieAuthBearer(
introspectionEndpoint: "https://auth.example.com/introspect",
introspectionToken: "resource-server-token");| Option | Default | Description |
|---|---|---|
ClientId |
Required | URI where the IndieAuth server can fetch client details |
CallbackPath |
Required | Path where the middleware intercepts the callback |
SignInScheme |
Required | Scheme used to persist the session (e.g., cookies) |
SaveTokens |
false |
Store access/refresh tokens in auth properties |
CacheDiscoveryResults |
true |
Enable discovery result caching |
DiscoveryCacheExpiration |
5 min | Cache TTL for discovery results |
UseHeadRequestForDiscovery |
false |
Use HEAD request optimization |
StrictProfileUrlValidation |
true |
Enforce strict profile URL validation (Section 3.2) |
EnableAuthorizationServerConfirmation |
true |
Verify auth server claims about me URL (Section 5.4) |
ValidateIssuer |
true |
Validate iss parameter per RFC 9207 |
MapProfileToClaims |
true |
Map profile data to OIDC-compatible claims |
| Property | Description |
|---|---|
Me |
The domain/URL the visitor is authenticating with |
Scope |
Array of scopes to request (e.g., profile, email, create) |
RedirectUri |
Where to redirect after authentication |
The library provides standalone services for token management:
var introspectionService = new TokenIntrospectionService(httpClient);
var result = await introspectionService.IntrospectTokenAsync(
"https://auth.example.com/introspect",
accessToken,
IntrospectionAuthMethod.Bearer,
authToken: "resource-server-token");
if (result.Success && result.Active)
{
var userUrl = result.Me;
var scopes = result.Scope;
var clientId = result.ClientId;
}var refreshService = new TokenRefreshService(httpClient);
var result = await refreshService.RefreshTokenAsync(
tokenEndpoint, refreshToken, clientId, scope);
if (result.Success)
{
var newAccessToken = result.AccessToken;
var newRefreshToken = result.RefreshToken; // May be rotated
}var revocationService = new TokenRevocationService(httpClient);
// Standard revocation endpoint
var result = await revocationService.RevokeTokenAsync(
revocationEndpoint, accessToken, tokenTypeHint: "access_token");
// Legacy method (for older servers)
var result = await revocationService.RevokeTokenLegacyAsync(
tokenEndpoint, accessToken);if (!string.IsNullOrEmpty(discoveryResult.UserinfoEndpoint))
{
var userinfoService = new UserinfoService(httpClient);
var result = await userinfoService.GetUserinfoAsync(
discoveryResult.UserinfoEndpoint, accessToken);
if (result.Success)
{
var name = result.Profile?.Name;
var email = result.Profile?.Email;
}
}var discoveryService = new IndieAuthDiscoveryService(httpClient);
var result = await discoveryService.DiscoverEndpointsAsync("https://example.com/");
if (result.Success)
{
var authEndpoint = result.AuthorizationEndpoint;
var tokenEndpoint = result.TokenEndpoint;
var issuer = result.Issuer;
}// Transforms user input to valid URL
"example.com".Canonicalize(); // → "https://example.com/"
"https://EXAMPLE.COM".Canonicalize(); // → "https://example.com/"var result = "https://example.com/user".IsValidProfileUrl();
if (!result.IsValid)
{
Console.WriteLine($"Invalid: {result.ErrorMessage}");
}When MapProfileToClaims is enabled, profile data is mapped to standard claims:
| Profile Field | Claim Type |
|---|---|
me |
me (custom) |
name |
name |
url |
website |
photo |
picture |
email |
email |
| - | email_verified (always false per spec) |
This library implements the IndieAuth Living Standard (July 2024):
| Section | Feature | Status |
|---|---|---|
| 3.2 | Profile URL Validation | ✅ |
| 3.4 | URL Canonicalization | ✅ |
| 4.1 | Discovery by Clients | ✅ |
| 5.2 | PKCE (S256) | ✅ |
| 5.2.1 | Issuer Validation (RFC 9207) | ✅ |
| 5.3.4 | Profile Information | ✅ |
| 5.4 | Authorization Server Confirmation | ✅ |
| 5.5 | Token Refresh | ✅ |
| 6 | Token Introspection | ✅ |
| 7 | Token Revocation | ✅ |
| 9 | Userinfo Endpoint | ✅ |
Contributions are very welcome!
- Fix an existing issue and submit a pull request
- Review open pull requests
- Report a new issue
- Make a suggestion / contribute to a discussion
This project is based on the OAuth handler in the aspnetcore repository.
MIT