AuthGateway is an authentication API developed in .NET 9 that acts as a Facade for the Supabase identity provider. The project provides a centralized abstraction layer that simplifies integration with authentication services, enabling authentication operations without directly exposing the underlying IDP's complexity.
This project implements the Facade pattern to:
- β Abstract the complexity of Supabase GoTrue
- β Provide a unified interface for authentication operations
- β Simplify future identity provider changes
- β Centralize authentication and validation logic
- β Securely manage JWT tokens
The project follows a layered architecture with clear separation of concerns:
AuthGateway/
βββ src/
β βββ API/ # Presentation Layer (FastEndpoints)
β β βββ Features/ # REST Endpoints
β β β βββ Auth/
β β β β βββ SignUp/ # Register new user
β β β β βββ SignIn/ # Login user
β β β β βββ SignInFromRefreshToken/ # Renew tokens
β β β βββ Users/ # User operations
β β βββ Security/ # JWT validation and security
β β βββ Program.cs # Application configuration
β β βββ appsettings.json # Configuration files
β β
β βββ application/ # Application Layer (Use Cases)
β β βββ Contracts/ # Interfaces (IAuthService)
β β βββ Features/ # Business use cases
β β βββ Auth/ # Authentication use cases
β β βββ Profiles/ # Profile operations
β β
β βββ domain/ # Domain Layer (Entities)
β β βββ Entities/
β β βββ Profiles/ # Domain models
β β
β βββ Infrastructure/ # Infrastructure Layer
β βββ DependencyInjection.cs # IoC Container
β βββ Configuration/ # Supabase Factory
β βββ Security/ # JWT/JWKS Validation
β βββ Application/
β βββ Contracts/ # Implementations (SupabaseAuthService)
- Facade Pattern:
IAuthServiceandSupabaseAuthServiceabstract Supabase - Dependency Injection: IoC Container for dependency management
- Clean Architecture: Clear separation of concerns
- SOLID Principles: Interfaces and dependency injection
The Facade is a pattern that provides a simplified interface to a complex subsystem. In this project:
// Without Facade (complex):
var client = new Supabase.Client(...)
var response = await client.Auth.SignUp(email, password, options);
// ... validate responses, handle exceptions, manage tokens...
// With Facade (simple):
await authService.SignUpAsync(email, password, metadata);
// Complexity is encapsulated in SupabaseAuthServiceHTTP Client
β
[API - FastEndpoints] (Presentation Layer)
β
[SignUpUseCase / SignInUseCase] (Application Layer)
β
[IAuthService Interface] (Contract - Abstraction)
β
[SupabaseAuthService] (Implementation - Facade)
β
[Supabase GoTrue] (Real Identity Provider)
- Provider Independence: Switching from Supabase to another IDP requires only implementing
IAuthService - Security: Centralized validations and conversions
- Reusability:
IAuthServicecan be injected anywhere - Testability: Easy to create mocks for unit testing
POST /Auth/SignUp
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure_password",
"userMetadata": {
"firstName": "John",
"lastName": "Silva"
}
}Response:
{
"accessToken": "eyJhbGc...",
"refreshToken": "refresh_token..."
}POST /Auth/SignIn
Content-Type: application/json
{
"email": "user@example.com",
"password": "secure_password"
}POST /Auth/SignInFromRefreshToken
Content-Type: application/json
{
"refreshToken": "refresh_token..."
}Generates new accessToken and refreshToken without requiring credentials.
GET /Users/GetProfileById/{id}
Authorization: Bearer {accessToken}Requires valid JWT authentication.
// appsettings.Development.json
{
"Supabase": {
"Url": "https://your-project.supabase.co",
"AnonKey": "your-anonymous-key",
"ServiceRoleKey": "your-service-role-key",
"JwksUrl": "https://your-project.supabase.co/auth/v1/jwks",
"Issuer": "https://your-project.supabase.co/auth/v1",
"Audience": "authenticated"
}
}# Navigate to project directory
cd /home/alberth/RiderProjects/AuthGateway
# Restore NuGet packages
dotnet restore
# Build the solution
dotnet build
# Run the API
dotnet run --project src/API| Package | Version | Purpose |
|---|---|---|
Supabase |
1.1.1 | Official Supabase client |
FastEndpoints |
5.35.0 | REST framework alternative to ASP.NET MVC |
FastEndpoints.Swagger |
5.35.0 | OpenAPI/Swagger documentation |
Microsoft.AspNetCore.Authentication.JwtBearer |
9.0.11 | JWT validation |
.NET |
9.0 | Base framework |
The API automatically validates JWT tokens using:
- JWKS (JSON Web Key Set): Obtains public keys from Supabase
- IssuerSigningKeyResolver: Resolves keys by
kid(Key ID) - Validation Parameters:
- Valid issuer
- Correct audience
- Expiration time
- Verified signature
builder.Services
.AddOptions<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme)
.Configure<SupabaseJwtSigningKeyResolver, IConfiguration>(
(opt, resolver, configuration) =>
{
opt.TokenValidationParameters = new()
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = configuration["Supabase:Issuer"],
ValidAudience = configuration["Supabase:Audience"],
IssuerSigningKeyResolver = (_, __, kid, ___) => resolver.ResolveByKid(kid),
RequireExpirationTime = true,
ClockSkew = TimeSpan.Zero,
};
}
);The Facade allows easy mock creation for testing:
// Mock IAuthService for testing
public class MockAuthService : IAuthService
{
public async Task<(string AccessToken, string RefreshToken)> SignUpAsync(
string email,
string password,
Dictionary<string, object>? userMetadata,
CancellationToken cancellationToken = default)
{
return ("mock-access-token", "mock-refresh-token");
}
// ... implement other methods ...
}
// Use in test
var mockService = new MockAuthService();
var result = await mockService.SignUpAsync("test@example.com", "password123");
Assert.Equal("mock-access-token", result.AccessToken);The infrastructure layer centralizes all dependencies:
// DependencyInjection.cs
services.AddScoped<IAuthService, SupabaseAuthService>(opt =>
{
var (auth, options) = SupabaseClientFactory.CreateStatelessClient(configuration);
return new SupabaseAuthService(auth, options);
});
services.AddScoped<SignUpUseCase>();
services.AddScoped<SignInUseCase>();
// ... other dependencies ...This allows:
- Easy implementation swapping
- Testing without real dependencies
- Clean and decoupled code
-
Add global error handlers
app.UseExceptionHandler("/error");
-
Implement Rate Limiting
app.UseRateLimiting();
-
Add structured logging
builder.Services.AddLogging();
-
Implement multiple IDP providers
IAuthService auth = idpType switch { IDPType.Supabase => new SupabaseAuthService(...), IDPType.Auth0 => new Auth0AuthService(...), IDPType.Okta => new OktaAuthService(...), _ => throw new NotSupportedException() };
-
Add refresh token rotation
- Invalidate old refresh tokens after renewal
-
Implement 2FA (Two-Factor Authentication)
- TOTP/SMS for enhanced security
Example: SignUpUseCase
public class SignUpUseCase
{
private readonly IAuthService _authService;
public SignUpUseCase(IAuthService authService)
{
_authService = authService; // Facade injection
}
public async Task<SignUpResponse> ExecuteAsync(
string email,
string password,
CancellationToken cancellationToken)
{
// Business validations
if (!email.Contains("@"))
throw new InvalidEmailException();
// Call the Facade
var (accessToken, refreshToken) = await _authService.SignUpAsync(
email,
password,
null,
cancellationToken
);
return new SignUpResponse(accessToken, refreshToken);
}
}| Method | Endpoint | Authentication | Description |
|---|---|---|---|
| POST | /Auth/SignUp |
β No | Register new user |
| POST | /Auth/SignIn |
β No | User login |
| POST | /Auth/SignInFromRefreshToken |
β No | Renew tokens |
| GET | /Users/GetProfileById/{id} |
β Yes | Get user profile |
The Swagger documentation is available at:
https://localhost:7xxx/swagger
(Port varies according to launchSettings.json)
- Language: C# 13
- Framework: .NET 9
- Web: FastEndpoints 5.35.0
- Authentication: Supabase GoTrue + JWT Bearer
- IDP: Supabase
- Patterns: Clean Architecture, Facade Pattern, SOLID
This is an educational example of how to implement a Facade for an IDP. Feel free to adapt it to your needs.
Q: Why use Facade instead of using Supabase directly?
A: The Facade provides abstraction, making it easy to switch IDPs in the future without breaking application code.
Q: How do I integrate with my frontend?
A: Use the POST endpoints for authentication and store tokens (localStorage/sessionStorage). Include the
accessTokenin theAuthorization: Bearer {token}header.
Q: What if the refresh token expires?
A: The user needs to log in again with their credentials.
Q: Can I add support for OAuth (Google, GitHub)?
A: Yes! Add methods to
IAuthServicelikeSignInWithProviderAsync()and implement inSupabaseAuthService.