PrimusSaaS.Identity.Validator
1.3.2
See the version list below for details.
dotnet add package PrimusSaaS.Identity.Validator --version 1.3.2
NuGet\Install-Package PrimusSaaS.Identity.Validator -Version 1.3.2
<PackageReference Include="PrimusSaaS.Identity.Validator" Version="1.3.2" />
<PackageVersion Include="PrimusSaaS.Identity.Validator" Version="1.3.2" />
<PackageReference Include="PrimusSaaS.Identity.Validator" />
paket add PrimusSaaS.Identity.Validator --version 1.3.2
#r "nuget: PrimusSaaS.Identity.Validator, 1.3.2"
#:package PrimusSaaS.Identity.Validator@1.3.2
#addin nuget:?package=PrimusSaaS.Identity.Validator&version=1.3.2
#tool nuget:?package=PrimusSaaS.Identity.Validator&version=1.3.2
Primus SaaS Identity Validator - .NET SDK
Official .NET SDK for validating JWT/OIDC tokens from your configured identity providers (Azure AD, Auth0, Cognito, Google, or any JWT issuer). The package is library-only: no Primus-hosted login, no Primus-issued tokens, no outbound calls to Primus.
Full client integration guide (Node + .NET + Logging): see
docs-site/docs/modules/client-integration-guide.md.
📋 Requirements
Supported Frameworks
| Framework | Status | Notes |
|---|---|---|
| .NET 8.0 | ✅ Supported | Recommended for new projects |
| .NET 7.0 | ✅ Supported | Full feature parity |
| .NET 6.0 | ✅ Supported | LTS - production ready |
SDK Requirements
Important: The .NET SDK version must match or exceed your project's target framework.
| Your Project Targets | Required SDK | Download |
|---|---|---|
| .NET 8.0 | .NET SDK 8.0+ | Download |
| .NET 7.0 | .NET SDK 7.0+ | Download |
| .NET 6.0 | .NET SDK 6.0+ | Download |
Check your SDK version:
dotnet --version
Common Issue: If you see NETSDK1045: The current .NET SDK does not support targeting .NET X.0, install the matching SDK version above.
Installation
dotnet add package PrimusSaaS.Identity.Validator
Or via NuGet Package Manager:
Install-Package PrimusSaaS.Identity.Validator
🚀 Auth0 Quick Start (5 Minutes)
New to Auth0? Follow these steps to secure your API in under 5 minutes.
Step 1: Sign up for Auth0 (FREE)
- Go to https://auth0.com/signup
- Create account (use Google/GitHub for fastest setup)
- Choose a tenant name (e.g.,
my-app→my-app.auth0.com)
Step 2: Create an API in Auth0
- Go to Dashboard → Applications → APIs → Create API
- Name:
My API(or your app name) - Identifier:
https://my-api(this becomes your Audience) - Click Create
Step 3: Install the Package
dotnet add package PrimusSaaS.Identity.Validator
Step 4: Configure Your API (Program.cs)
using PrimusSaaS.Identity.Validator;
var builder = WebApplication.CreateBuilder(args);
// Add Auth0 authentication with one line
builder.Services.AddPrimusIdentity(options =>
{
options.UseAuth0(
domain: "my-app.auth0.com", // From Step 1
audience: "https://my-api"); // From Step 2
});
builder.Services.AddControllers();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Step 5: Protect Your Endpoints
[ApiController]
[Route("api/[controller]")]
[Authorize] // Requires valid Auth0 token
public class SecureController : ControllerBase
{
[HttpGet]
public IActionResult Get() => Ok(new { message = "Hello, authenticated user!" });
}
Step 6: Get a Test Token
- Go to Dashboard → Applications → APIs → My API → Test tab
- Copy the test token
- Test your API:
curl -H "Authorization: Bearer YOUR_TOKEN_HERE" https://localhost:5001/api/secure
✅ Done! Your API is now secured with Auth0.
Quick Start (General)
1. Configure in Program.cs or Startup.cs
using PrimusSaaS.Identity.Validator;
var builder = WebApplication.CreateBuilder(args);
// Add Primus Identity validation (multi-issuer)
builder.Services.AddPrimusIdentity(options =>
{
options.Issuers = new()
{
new IssuerConfig
{
Name = "AzureAD",
Type = IssuerType.AzureAD, // Alias for OIDC (Azure-friendly)
Issuer = "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
Authority = "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
Audiences = new List<string> { "api://your-api-id" }
},
new IssuerConfig
{
Name = "LocalAuth",
Type = IssuerType.Jwt,
Issuer = "https://auth.yourcompany.com",
Secret = "your-local-secret",
Audiences = new List<string> { "api://your-api-id" }
}
};
options.ValidateLifetime = true;
options.RequireHttpsMetadata = true; // Set false for local dev only
options.ClockSkew = TimeSpan.FromMinutes(5);
// Optional: map claims to tenant context
options.TenantResolver = claims => new TenantContext
{
TenantId = claims.Get("tid") ?? "default",
Roles = claims.Get<List<string>>("roles") ?? new List<string>()
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
Auth0 (simple helper)
builder.Services.AddPrimusIdentity(options =>
{
// One-liner with sane defaults (issuer/audience/lifetime validation on)
options.UseAuth0(
domain: "your-tenant.auth0.com",
audience: "https://your-api-identifier",
auth0 =>
{
// Optional: map namespaced roles into [Authorize(Roles="...")]
auth0.RoleClaimName = "https://your-api-identifier/roles";
// Optional: require an organization claim/value
auth0.ValidateOrganization = true;
auth0.RequiredOrganization = "org_abc123";
});
// Add other providers alongside Auth0
options.Issuers.Add(new IssuerConfig
{
Name = "AzureAD",
Type = IssuerType.AzureAD,
Issuer = "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
Authority = "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
Audiences = new List<string> { "api://your-api-id" }
});
});
Auth0 permissions/org policies
builder.Services.AddAuthorization(options =>
{
// Require ALL listed permissions
options.RequireAuth0Permissions("CanManageClients", "read:clients", "write:clients");
// Require ANY listed permission
options.RequireAnyAuth0Permission("CanReadClients", "read:clients", "read:all");
// Require an organization claim/value (set ValidateOrganization = true in Auth0 config)
options.AddPrimusClaimPolicy("RequireOrg", "org_id");
});
Auth0 permission attributes (controller-level)
using PrimusSaaS.Identity.Validator;
[Auth0Permission("read:clients")]
public async Task<IActionResult> GetClients() { ... }
[Auth0Permissions("read:clients", "write:clients")]
public async Task<IActionResult> UpdateClient() { ... } // requires BOTH
[Auth0AnyPermission("read:clients", "read:all")]
public async Task<IActionResult> GetClient() { ... } // requires ANY
[RequireOrganization] // requires org claim (default org_id) to be present
public async Task<IActionResult> OrgScoped() { ... }
[RequireOrganization("org_abc123")] // requires specific org value
public async Task<IActionResult> SpecificOrgOnly() { ... }
Google OIDC (ID tokens)
builder.Services.AddPrimusIdentity(options =>
{
options.UseGoogle(audience: "<google-client-id>");
// Add other providers as needed (AzureAD/Auth0/Local)
});
// AWS Cognito user pool
builder.Services.AddPrimusIdentity(options =>
{
options.UseCognito(
region: "us-east-1",
userPoolId: "us-east-1_ABC123",
audience: "<app-client-id>",
cognito =>
{
cognito.RoleClaimName = "cognito:groups"; // optional, maps into ClaimTypes.Role
});
});
// Machine-to-machine & email verification (Auth0 example)
builder.Services.AddPrimusIdentity(options =>
{
options.UseAuth0("your-tenant.auth0.com", "https://your-api-identifier", auth0 =>
{
auth0.AllowMachineToMachine = true;
auth0.AllowedGrantTypes.Add("client-credentials");
auth0.AllowedMachineToMachineScopes.AddRange(new[] { "read:clients", "write:clients" });
auth0.RequireEmailVerification = true; // for user tokens
});
});
M2M scope enforcement
- Set
AllowMachineToMachine = trueandAllowedMachineToMachineScopesto restrict scopes on client-credentials tokens. - Tokens with scopes outside the allowed list will be rejected.
Local/Test token generation
// Generate a test JWT (HMAC) for local or integration tests
var token = TestTokenBuilder.Create()
.WithIssuer("https://localhost")
.WithAudience("api://your-api-id")
.WithSecret("local-secret") // match your IssuerConfig secret when validating
.WithClaim("sub", "user-123")
.WithClaim("email", "test@example.com")
.Build();
Using the built-in fake handler (for integration tests)
// In your test host setup (WebApplicationFactory, minimal API, etc.)
services.AddFakePrimusAuth(); // from PrimusSaaS.Identity.Validator.Tests.IntegrationHarness
app.UseFakePrimusAuth();
This authenticates requests with a fixed user (sub, email, name) so you can test APIs without an external IdP. See examples/dotnet-api/FakeAuthApi for a runnable sample.
Logging & diagnostics
- Configure logging verbosity and redaction via
options.Logging:MinimumLevel(default: Information)RedactSensitiveData(default: true)LogValidationSteps(default: true)LogClaimMapping(default: false)
- Expose diagnostics endpoint with
app.MapPrimusIdentityDiagnostics(); - Structured logging: when
LogValidationStepsis true, issuer/audience/kid are logged; subjects are hashed when redaction is on. - Refresh tokens: set
TokenRefresh.UseDurableStore = trueand registerIRefreshTokenStore(e.g.,DistributedRefreshTokenStorefor Redis/SQL viaIDistributedCache).
Multi-provider (Azure AD + Auth0 + Local)
builder.Services.AddPrimusIdentity(options =>
{
// Azure AD
options.Issuers.Add(new IssuerConfig
{
Name = "AzureAD",
Type = IssuerType.AzureAD,
Issuer = "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
Authority = "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
Audiences = new List<string> { "api://your-api-id" }
});
// Auth0 (one-line helper)
options.UseAuth0("your-tenant.auth0.com", "https://your-api-identifier", auth0 =>
{
auth0.RoleClaimName = "https://your-api-identifier/roles"; // optional
auth0.ValidateOrganization = true;
auth0.RequiredOrganization = "org_abc123";
});
// Local JWT (shared secret)
options.Issuers.Add(new IssuerConfig
{
Name = "LocalAuth",
Type = IssuerType.Jwt,
Issuer = "https://auth.yourcompany.com",
Secret = "your-local-secret",
Audiences = new List<string> { "api://your-api-id" }
});
});
Auth0 multi-tenant (resolve per request)
builder.Services.AddPrimusIdentity(options =>
{
options.Auth0MultiTenant = new Auth0MultiTenantOptions
{
ResolveTenant = ctx =>
{
// Example: subdomain-based tenant routing
var host = ctx.Request.Host.Host;
return host.Split('.').FirstOrDefault();
}
};
options.Auth0MultiTenant.Tenants["client-a"] = new Auth0Options
{
Domain = "client-a.auth0.com",
Audiences = { "https://api-client-a" }
};
options.Auth0MultiTenant.Tenants["client-b"] = new Auth0Options
{
Domain = "client-b.auth0.com",
Audiences = { "https://api-client-b" }
};
});
2. Protect Your API Endpoints
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PrimusSaaS.Identity.Validator;
[ApiController]
[Route("api/[controller]")]
public class SecureController : ControllerBase
{
[HttpGet]
[Authorize] // Requires valid token from a configured issuer
public IActionResult GetSecureData()
{
// Get the authenticated Primus user
var primusUser = HttpContext.GetPrimusUser();
return Ok(new
{
message = "Secure data accessed successfully",
user = new
{
userId = primusUser?.UserId,
email = primusUser?.Email,
name = primusUser?.Name,
roles = primusUser?.Roles
}
});
}
[HttpGet("admin")]
[Authorize(Roles = "Admin")] // Requires Admin role from your IdP
public IActionResult GetAdminData()
{
return Ok(new { message = "Admin-only data" });
}
}
3. Access User Information
// In any controller or middleware
var primusUser = HttpContext.GetPrimusUser();
if (primusUser != null)
{
Console.WriteLine($"User ID: {primusUser.UserId}");
Console.WriteLine($"Email: {primusUser.Email}");
Console.WriteLine($"Name: {primusUser.Name}");
Console.WriteLine($"Roles: {string.Join(", ", primusUser.Roles)}");
// Access additional claims
foreach (var claim in primusUser.AdditionalClaims)
{
Console.WriteLine($"{claim.Key}: {claim.Value}");
}
}
Configuration Options
| Option | Required | Description | Default |
|---|---|---|---|
| Issuers | Yes | List of issuer configs (Oidc/AzureAD or Jwt) | - |
| ValidateLifetime | No | Validate token expiration | true |
| RequireHttpsMetadata | No | Require HTTPS for metadata | true |
| ClockSkew | No | Allowed time difference | 5 minutes |
| JwksCacheTtl | No | JWKS cache TTL (OIDC) | 24 hours |
| TenantResolver | No | Map claims to TenantContext | null |
IssuerConfig
| Field | Required | Description |
|---|---|---|
| Name | Yes | Friendly name (e.g., AzureAD, LocalAuth) |
| Type | Yes | Oidc or Jwt |
| Issuer | Yes | Expected iss value to route tokens |
| Authority | OIDC only | Authority URL for discovery/JWKS |
| JwksUrl | JWT optional | JWKS endpoint (if not using Secret) |
| Secret | JWT optional | Symmetric key for HMAC tokens |
| Audiences | Yes | Allowed audience values |
| ClaimMappings | No | Map provider claims to standard claim types |
| RoleClaimName | No | If set, mapped into ClaimTypes.Role |
| PermissionClaimName | No | Permission claim to normalize (defaults to permissions) |
| OrganizationClaimName | No | Organization claim to normalize (defaults to org_id) |
| ValidateOrganization | No | Require org claim presence (and optional value) |
| RequiredOrganization | No | Specific organization value to require when ValidateOrganization = true |
Configuration from appsettings.json
{
"PrimusIdentity": {
"Issuers": [
{
"Name": "AzureAD",
"Type": "Oidc",
"Issuer": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
"Authority": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
"Audiences": [ "api://your-api-id" ]
},
{
"Name": "LocalAuth",
"Type": "Jwt",
"Issuer": "https://auth.yourcompany.com",
"Secret": "your-local-secret",
"Audiences": [ "api://your-api-id" ]
}
],
"RequireHttpsMetadata": false
}
}
Generating Tokens for Local JWT Issuer
Important: The Secret, Issuer, and Audience values used when generating tokens MUST EXACTLY MATCH your validator configuration.
Quick Example
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
public string GenerateLocalJwtToken(string userId, string email, string name)
{
// Critical: Load from same configuration source
var secret = _config["PrimusIdentity:Issuers:1:Secret"];
var issuer = _config["PrimusIdentity:Issuers:1:Issuer"];
var audience = _config["PrimusIdentity:Issuers:1:Audiences:0"];
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.UTF8.GetBytes(secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim("sub", userId),
new Claim("email", email),
new Claim("name", name)
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = issuer,
Audience = audience,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature
)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
For complete token generation examples, see TOKEN_GENERATION_GUIDE.md
Troubleshooting
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Invalid signature | Secret key mismatch | Ensure token generation and validation use the same secret |
| Untrusted issuer | Issuer format incorrect | Use full URL format (e.g., https://localhost:5265) not name |
| Invalid audience | Audience mismatch | Use API identifier format (e.g., api://your-app-id) |
| Token expired | Token past expiration | Generate new token or increase ClockSkew |
For detailed troubleshooting, see ERROR_REFERENCE.md
Migrating from JwtBearer/Auth0 SDK
- You can swap existing
AddJwtBearerAuth0 config foroptions.UseAuth0(domain, audience, ...)without changing your controllers; permissions/roles map into standard claims. - Auth0 namespaced roles: set
RoleClaimNameto your namespaced roles claim and[Authorize(Roles = "...")]will work. - Permissions: use policies (
RequireAuth0Permissions/RequireAnyAuth0Permission) or attributes (Auth0Permission/Auth0Permissions/Auth0AnyPermission). - Minimal migration snippet:
// Before builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(opt => { opt.Authority = "https://your-tenant.auth0.com/"; opt.Audience = "https://your-api-identifier"; }); // After builder.Services.AddPrimusIdentity(options => { options.UseAuth0("your-tenant.auth0.com", "https://your-api-identifier", auth0 => { auth0.RoleClaimName = "https://your-api-identifier/roles"; // if you had roles mapped }); }); builder.Services.AddAuthorization(); - Migration helper:
var auth0 = JwtBearerMigrationHelper.ToAuth0Options(new JwtBearerMigrationHelper.JwtBearerConfig { Authority = "https://your-tenant.auth0.com/", Audience = "https://your-api-identifier", RoleClaimName = "https://your-api-identifier/roles" }); var options = new PrimusIdentityOptions(); options.Issuers.Add(auth0.ToIssuerConfig());
Production Deployment
Caution: Never commit secrets to source control! Use Azure Key Vault or environment variables.
Quick Checklist
- Secrets stored in Azure Key Vault
- RequireHttpsMetadata: true in production
- HTTPS redirection enabled
- CORS configured for production domains
- Logging and monitoring configured
For complete deployment guide, see PRODUCTION_DEPLOYMENT.md
Client Usage Example
To call your protected API from a client application:
using System.Net.Http.Headers;
var httpClient = new HttpClient();
var jwtToken = "your-jwt-token-here"; // From your auth system or token generator
// Add token to Authorization header
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", jwtToken);
var response = await httpClient.GetAsync("https://your-api.com/api/secure");
var data = await response.Content.ReadAsStringAsync();
Development Tips
Disable HTTPS Requirement for Local Development
builder.Services.AddPrimusIdentity(options =>
{
options.RequireHttpsMetadata = false; // Allow HTTP in development
// ... other options
});
Enable Detailed Logging
The SDK automatically logs authentication events to the console. For more detailed logging, enable ASP.NET Core logging:
{
"Logging": {
"LogLevel": {
"Microsoft.AspNetCore.Authentication": "Debug"
}
}
}
Requirements
- .NET 7.0 or later
- ASP.NET Core 7.0 or later
Known Issues
Namespace Conflict with PrimusSaaS.Logging
If you are using both PrimusSaaS.Identity.Validator and PrimusSaaS.Logging, you may encounter an ambiguous reference error for UsePrimusLogging().
Status: Fixed in PrimusSaaS.Logging >= 1.2.2 (duplicate extension removed). Simply import using PrimusSaaS.Logging.Extensions; and call:
app.UsePrimusLogging();
If you cannot upgrade Logging yet: Use the fully qualified name or an alias.
using PrimusLogging = PrimusSaaS.Logging.Extensions;
// ...
PrimusLogging.LoggingExtensions.UsePrimusLogging(app);
Documentation
- TOKEN_GENERATION_GUIDE.md - Complete guide to generating JWT tokens
- LOCAL_DEVELOPMENT_GUIDE.md - Setup guide for offline/local development
- INTEGRATION_PATTERNS.md - Controller, Service, and Middleware examples
- TENANT_RESOLVER_GUIDE.md - Multi-tenant context resolution guide
- PRIMUS_USER_REFERENCE.md - PrimusUser object properties and mapping
- ERROR_HANDLING_GUIDE.md - Handling exceptions and customizing responses
- ERROR_REFERENCE.md - Troubleshooting validation errors
- PRODUCTION_DEPLOYMENT.md - Production deployment best practices
- SECRET_MANAGEMENT.md - Securely managing secrets (Key Vault, User Secrets)
- TESTING_GUIDE.md - Testing guide with Postman & Integration Tests
- CLAIMS_MAPPING.md - Reference for required and optional claims
- ANGULAR_INTEGRATION.md - Integration guide for Angular applications
Support
For issues, questions, or contributions, visit:
- GitHub: https://github.com/primus-saas/identity-validator
- Documentation: https://docs.primus-saas.com
License
MIT License - see LICENSE file for details
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 6.0.36)
- Microsoft.Extensions.Options (>= 7.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
-
net7.0
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 7.0.20)
- Microsoft.Extensions.Options (>= 7.0.0)
- System.IdentityModel.Tokens.Jwt (>= 8.14.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0 | 107 | 1/12/2026 |
| 1.5.0 | 313 | 12/3/2025 |
| 1.3.6 | 274 | 11/30/2025 |
| 1.3.5 | 234 | 11/30/2025 |
| 1.3.3 | 239 | 11/29/2025 |
| 1.3.2 | 126 | 11/29/2025 |
| 1.3.1 | 137 | 11/28/2025 |
| 1.3.0 | 206 | 11/24/2025 |
| 1.2.3 | 200 | 11/24/2025 |
| 1.2.2 | 200 | 11/24/2025 |
| 1.2.1 | 197 | 11/24/2025 |
| 1.2.0 | 190 | 11/23/2025 |
| 1.1.0 | 158 | 11/23/2025 |
| 1.0.0 | 317 | 11/21/2025 |
v1.3.2:
- IMPROVED: Enhanced error messages with actionable guidance and documentation links
- IMPROVED: Auth0 configuration validation now provides step-by-step troubleshooting
- DOCS: Added 5-minute Auth0 Quick Start guide to README
- DOCS: Added clear SDK requirements table (which SDK version for which .NET target)
- DOCS: Clarified supported frameworks (.NET 6.0, 7.0, 8.0)
- NEW: PrimusIdentityConfigurationException with HelpUrl and PropertyName for better debugging
v1.3.1:
- Production release with Auth0 support via UseAuth0() helper
- Organization validation and M2M token support
- Full multi-issuer support (Azure AD, Auth0, Cognito, Google, custom JWT)