Skip to content

myquay/IndieAuth

Repository files navigation

IndieAuth .NET

An ASP.NET Core authentication handler adding support for authenticating visitors using the IndieAuth protocol.

NuGet .NET

Supported Platforms

  • .NET 8.0 (LTS)
  • .NET 10.0

Installation

This library is distributed as a NuGet package. To install with the .NET CLI:

dotnet add package AspNet.Security.IndieAuth

Quick Start

Configure 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);

Bearer Token Authentication for APIs

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(' ');
        // ...
    }
}

Minimal API Support

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");

Configuration Options

IndieAuthOptions

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

IndieAuthChallengeProperties

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

Standalone Services

The library provides standalone services for token management:

Token Introspection

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;
}

Token Refresh

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
}

Token Revocation

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);

Userinfo

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;
    }
}

Discovery

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;
}

URL Utilities

Canonicalization (Section 3.4)

// Transforms user input to valid URL
"example.com".Canonicalize();           // → "https://example.com/"
"https://EXAMPLE.COM".Canonicalize();   // → "https://example.com/"

Profile URL Validation (Section 3.2)

var result = "https://example.com/user".IsValidProfileUrl();
if (!result.IsValid)
{
    Console.WriteLine($"Invalid: {result.ErrorMessage}");
}

Claims

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)

Specification Compliance

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

Contributing

Contributions are very welcome!

Ways to contribute

  • Fix an existing issue and submit a pull request
  • Review open pull requests
  • Report a new issue
  • Make a suggestion / contribute to a discussion

Acknowledgments

This project is based on the OAuth handler in the aspnetcore repository.

License

MIT

About

A NuGet library to add IndieAuth support to ASP.NET

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages