Auth0 to OpenIddict Migration¶
Overview¶
This document outlines the migration strategy from Auth0 to OpenIddict for authentication and authorization in SyRF. The migration is driven by cost considerations while maintaining all existing functionality.
Current State Analysis¶
Auth0 Features Currently Used¶
| Feature | Description | Migration Complexity |
|---|---|---|
| JWT Bearer Auth | Standard OIDC/OAuth2 with RS256 | Low - OpenIddict native support |
| Custom Domain | signin.syrf.org.uk |
Medium - DNS + certificate setup |
| Management API | User CRUD, email verification, password reset | High - Must implement equivalent |
| Social Logins | Google only (custom credentials) | Low - Reuse existing OAuth credentials |
| Identity Linking | Not currently used | N/A - Can add later if needed |
| Custom Claims | SYRF namespace claims in tokens | Low - OpenIddict supports this |
| Refresh Tokens | Offline access with rotation | Low - OpenIddict native support |
| Email Verification | Ticket-based verification links | Medium - Email service integration |
| Password Reset | Ticket-based reset links | Medium - Email service integration |
Files Requiring Changes¶
Backend (.NET) - High Impact:
- src/services/api/SyRF.API.Endpoint/Program.cs - JWT configuration
- src/services/api/SyRF.API.Endpoint/Services/Auth0Config.cs - Replace with OpenIddict config
- src/services/api/SyRF.API.Endpoint/Services/Auth0Service.cs - Replace with local user service
- src/services/api/SyRF.API.Endpoint/Services/AuthManagementApiClientProvider.cs - Remove (not needed)
- src/services/api/SyRF.API.Endpoint/Controllers/AccountController.cs - Major refactor
- src/services/api/SyRF.API.Endpoint/Handlers/Investigator*Handler.cs - Remove Auth0 sync
Frontend (Angular) - High Impact:
- src/services/web/src/main.ts - Replace Auth0 module
- src/services/web/src/app/core/auth/auth.effects.ts - Major refactor
- src/services/web/src/app/core/http-interceptors/auth-interceptor.service.ts - Replace
- src/services/web/package.json - Replace Auth0 packages
Shared Libraries - Low Impact:
- src/libs/kernel/SyRF.SharedKernel/Constants.cs - Keep claim types (minor updates)
- src/libs/webhostconfig/SyRF.WebHostConfig.Common/Infrastructure/CurrentUserService.cs - Minor updates
Architecture Decision: OpenIddict Deployment¶
Option A: New Identity Service (Recommended)¶
Create a dedicated identity server as a new service in the monorepo.
src/services/
├── api/ # Existing - becomes resource server only
├── project-management/ # Existing - resource server
├── identity/ # NEW - OpenIddict authorization server
│ ├── SyRF.Identity.Endpoint/
│ │ ├── Controllers/
│ │ │ ├── AuthorizationController.cs
│ │ │ ├── AccountController.cs # Moved from API
│ │ │ ├── UserInfoController.cs
│ │ │ └── TokenController.cs
│ │ ├── Services/
│ │ ├── Views/ # Login, consent, error pages
│ │ └── Program.cs
│ ├── SyRF.Identity.Core/
│ │ └── Entities/ # ApplicationUser, etc.
│ └── Dockerfile
├── web/
└── quartz/
Pros: - Clean separation of concerns - Independent scaling of identity service - Follows microservices best practices - Easier to maintain and update - Can be deployed to different infrastructure if needed
Cons: - New service to deploy and maintain - Additional infrastructure cost (minimal) - Cross-service database access for user data
Option B: Integrated with API Service¶
Embed OpenIddict server within the existing API service.
Pros: - Simpler deployment (no new service) - Direct database access for user data - Fewer moving parts
Cons: - Couples identity concerns with business logic - Harder to scale independently - Larger API service codebase - Not recommended by OpenIddict documentation
Recommendation: Option A (New Identity Service)¶
The identity service should be separate for: 1. Security isolation 2. Independent scaling 3. Cleaner architecture 4. Follows industry best practices
Database Strategy¶
Option 1: MongoDB (Recommended)¶
Use existing MongoDB infrastructure with OpenIddict MongoDB stores.
// OpenIddict with MongoDB
services.AddOpenIddict()
.AddCore(options =>
{
options.UseMongoDb()
.UseDatabase(mongoClient.GetDatabase("syrf-identity"));
});
Required Collections:
- OpenIddictApplications - Registered clients (web app, API)
- OpenIddictAuthorizations - Active authorizations
- OpenIddictScopes - Defined scopes
- OpenIddictTokens - Issued tokens (for revocation)
- IdentityUsers - User accounts (email, password hash, claims)
Packages:
- OpenIddict.MongoDb - MongoDB stores for OpenIddict
- AspNetCore.Identity.MongoDbCore - ASP.NET Core Identity with MongoDB
Option 2: Entity Framework Core with PostgreSQL¶
Add a new PostgreSQL database for identity data.
Pros: - More common OpenIddict setup - Better tooling for identity management
Cons: - Additional database to manage - Different technology stack - Migration complexity
Recommendation: Option 1 (MongoDB)¶
Continue using MongoDB for consistency with existing infrastructure.
User Migration Strategy¶
Phase 1: Data Export from Auth0¶
Export all users from Auth0 Management API:
{
"user_id": "auth0|abc123",
"email": "user@example.com",
"email_verified": true,
"name": "John Doe",
"given_name": "John",
"family_name": "Doe",
"picture": "https://...",
"identities": [
{
"connection": "Username-Password-Authentication",
"user_id": "abc123",
"provider": "auth0"
},
{
"connection": "google-oauth2",
"user_id": "google123",
"provider": "google-oauth2"
}
],
"app_metadata": {
"syrf_groups": "admin researcher"
}
}
Phase 2: Password Handling¶
Challenge: Auth0 password hashes cannot be exported (security feature).
Solutions:
- Password Reset Required (Recommended)
- Import users with email only
- Force password reset on first login
-
Send bulk password reset emails
-
Lazy Migration with Auth0 Fallback
- Keep Auth0 running temporarily
- On login failure in OpenIddict, try Auth0
- If Auth0 succeeds, migrate password hash
-
Eventually disable Auth0 fallback
-
Magic Link Only Initially
- Disable password login temporarily
- Use email magic links for authentication
- Allow users to set password after login
Recommendation: Option 1 (Password Reset Required)¶
Simplest and most secure approach. Users reset passwords once during migration.
Phase 3: Social Login Migration¶
Only Google social login is currently configured. Since SyRF uses custom Google OAuth credentials in Auth0 (not Auth0's built-in connection), these credentials can be directly transferred to OpenIddict.
services.AddAuthentication()
.AddGoogle(options =>
{
// Reuse existing credentials from Auth0
options.ClientId = "<existing-google-client-id>";
options.ClientSecret = "<existing-google-client-secret>";
});
Key Benefits of Credential Reuse: - ✅ Users will NOT see Google's consent screen again (consent is tied to Client ID) - ✅ Existing Google user IDs remain valid - ✅ Seamless experience for social login users
Migration Process: 1. Export Google OAuth Client ID and Secret from Auth0 Dashboard 2. Configure the same credentials in OpenIddict 3. Import external login mappings from Auth0 user export 4. Users can log in with Google immediately - no re-linking required
Implementation Plan¶
Stage 1: Identity Service Foundation¶
Duration: 1-2 weeks
- Create new
src/services/identity/service structure - Set up OpenIddict with MongoDB stores
- Configure ASP.NET Core Identity with MongoDB
- Implement basic endpoints:
/.well-known/openid-configuration/connect/authorize/connect/token/connect/userinfo- Create login/logout views (Razor Pages or MVC)
- Configure development certificates
- Add to Docker and Helm charts
Deliverables: - Working identity server with local user registration - OpenID Connect discovery endpoint - Authorization code flow working
Stage 2: Feature Parity¶
Duration: 1-2 weeks
- Email verification flow
- Password reset flow
- Profile management endpoints
- Custom claims configuration
- Refresh token support
- Social login providers (Google, Apple, Facebook)
Deliverables: - Email service integration - All account management features working - Social login working
Stage 3: API Service Updates¶
Duration: 1 week
- Update JWT Bearer configuration to use OpenIddict
- Remove Auth0-specific services
- Update authorization handlers (minimal changes)
- Update claim extraction (if namespace changes)
- Remove Auth0 sync handlers
- Update Swagger OAuth configuration
Deliverables: - API validates tokens from OpenIddict - All endpoints working with new auth
Stage 4: Frontend Updates¶
Duration: 1-2 weeks
- Replace
@auth0/auth0-angularwith OIDC client library - Update login/logout flows
- Update token handling
- Update HTTP interceptor
- Update user profile management
- Update ngrx auth state
Recommended Package: angular-auth-oidc-client
Deliverables: - Frontend authenticates with OpenIddict - All user flows working
Stage 5: User Migration¶
Duration: 1 week
- Export users from Auth0
- Import users to OpenIddict database
- Send password reset emails
- Monitor migration progress
- Handle edge cases
Deliverables: - All users migrated - Password reset emails sent - Login working for all users
Stage 6: Cutover and Decommission¶
Duration: 1 week
- Update DNS for
signin.syrf.org.uk - Switch production to OpenIddict
- Monitor for issues
- Keep Auth0 in read-only mode temporarily
- Decommission Auth0 after validation period
Deliverables: - Production running on OpenIddict - Auth0 decommissioned - Cost savings achieved
Technical Implementation Details¶
OpenIddict Server Configuration¶
// Program.cs for Identity Service
builder.Services.AddOpenIddict()
.AddCore(options =>
{
options.UseMongoDb()
.UseDatabase(mongoDatabase);
})
.AddServer(options =>
{
// Enable authorization code + refresh token flows
options.AllowAuthorizationCodeFlow()
.AllowRefreshTokenFlow();
// Enable PKCE (required for public clients)
options.RequireProofKeyForCodeExchange();
// Token endpoints
options.SetAuthorizationEndpointUris("/connect/authorize")
.SetTokenEndpointUris("/connect/token")
.SetUserinfoEndpointUris("/connect/userinfo")
.SetLogoutEndpointUris("/connect/logout")
.SetIntrospectionEndpointUris("/connect/introspect");
// Signing credentials
options.AddSigningKey(signingKey)
.AddEncryptionKey(encryptionKey);
// ASP.NET Core integration
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough()
.EnableTokenEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
.EnableLogoutEndpointPassthrough();
// Token lifetimes
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(60))
.SetRefreshTokenLifetime(TimeSpan.FromDays(14));
})
.AddValidation(options =>
{
options.UseLocalServer();
options.UseAspNetCore();
});
Custom Claims Configuration¶
// Add custom claims to tokens
public class CustomClaimsService : IOpenIddictServerHandler<ProcessSignInContext>
{
public async ValueTask HandleAsync(ProcessSignInContext context)
{
var identity = (ClaimsIdentity)context.Principal!.Identity!;
// Add SYRF-specific claims
identity.AddClaim("https://claims.syrf.org.uk/user_id", userId);
identity.AddClaim("https://claims.syrf.org.uk/syrf_groups", groups);
identity.AddClaim("https://claims.syrf.org.uk/given_name", givenName);
// ... etc
}
}
API JWT Validation Update¶
// Program.cs for API Service
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://signin.syrf.org.uk"; // Now points to OpenIddict
options.Audience = "https://syrf-api.syrf.org.uk";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true
};
// Keep SignalR token handling
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
path.StartsWithSegments("/notifications"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Angular OIDC Configuration¶
// Using angular-auth-oidc-client
import { AuthModule, LogLevel } from 'angular-auth-oidc-client';
@NgModule({
imports: [
AuthModule.forRoot({
config: {
authority: 'https://signin.syrf.org.uk',
redirectUrl: window.location.origin,
postLogoutRedirectUri: window.location.origin,
clientId: 'syrf-web',
scope: 'openid profile email offline_access',
responseType: 'code',
silentRenew: true,
useRefreshToken: true,
logLevel: LogLevel.Debug,
secureRoutes: ['https://api.syrf.org.uk'],
},
}),
],
})
export class AppModule {}
Risk Assessment¶
| Risk | Impact | Likelihood | Mitigation |
|---|---|---|---|
| Password migration issues | High | Medium | Bulk password reset emails, support process |
| Social login linking breaks | Medium | Medium | Clear user communication, re-linking flow |
| Token validation issues | High | Low | Thorough testing, gradual rollout |
| Performance degradation | Medium | Low | Load testing before cutover |
| User confusion | Medium | High | Clear communication, help documentation |
| Rollback needed | High | Low | Keep Auth0 active during transition |
Success Criteria¶
- Functional Parity: All Auth0 features replicated in OpenIddict
- User Migration: 100% of active users migrated successfully
- Zero Downtime: Seamless transition with no service interruption
- Performance: Token issuance < 200ms, validation < 50ms
- Security: Pass security review, proper key management
- Cost Savings: Auth0 subscription eliminated
Questions for User Decision¶
All Answered ✅¶
- User Count: 5,711 users to migrate
- Custom Domain: Keep
signin.syrf.org.uk✅ - Email Service: AWS SES for verification and password reset emails
- Social Login: Google only, using custom OAuth credentials (reusable - no consent screen) ✅
- Timeline: ASAP - targeting 6-8 week phased rollout
- Weeks 1-3: Build identity service, staging deployment
- Week 4: Frontend + API updates in staging
- Week 5: User migration to staging, internal testing
- Week 6: Production cutover
- Weeks 7-8: Monitoring period
- Rollback Plan: Keep Auth0 active for 4 weeks after production cutover
- Monitor login success rates
- All active users should log in at least once
- Decommission Auth0 after validation period
Next Steps¶
- Review and approve this migration plan
- Answer clarifying questions
- Create identity service project structure
- Set up OpenIddict with MongoDB
- Implement basic authorization endpoints
- Configure AWS SES for email
- Begin frontend migration preparation