External SSO Login Integration Skill
Overview
This skill generates production-ready C# code to add OpenID Connect external SSO login to your ABP Framework AuthServer module. It follows ABP best practices and is reusable across any ABP project.
What this skill does:
- ✅ Generates
ConfigureExternalAuthentication()method code - ✅ Adds method call to
ConfigureServices() - ✅ Handles SSL bypass for dev/staging
- ✅ Supports custom scopes
- ✅ Includes proper logout handling
- ✅ Feature flag controlled
What this skill does NOT do:
- ❌ Modify appsettings.json (you'll add your own configuration keys)
- ❌ Assume any specific provider (Google, Microsoft, etc.)
- ❌ Add UI components (your existing login page will automatically show external login button if configured)
Usage
/external-sso-login-integration [options]
Options
Configuration Keys (customize these to match your appsettings structure):
--config-section <name>- Configuration section name (default:ExternalSsoLogin)--authority-key <key>- Config key for authority URL (default:Authority)--client-id-key <key>- Config key for client ID (default:ClientId)--client-secret-key <key>- Config key for client secret (default:ClientSecret)--bypass-ssl-key <key>- Config key for SSL bypass flag (default:ByPassSSL)--redirect-uri-key <key>- Config key for redirect URI (default:RedirectUri)
Feature Flag:
--feature-flag <name>- Feature flag identifier (default:Authentication.OpenIDConnect)--auto-enable- Add code to auto-enable feature flag (for testing)
Scopes:
--scopes <list>- Comma-separated list of OIDC scopes (default:openid,profile,email,role)--custom-scopes <list>- Additional custom scopes (e.g.,ncell_scheduler_stg)
Code Generation:
--method-name <name>- Name of the configuration method (default:ConfigureExternalAuthentication)--insert-location <line>- Insert method call at specific line number (default: find best location)--dry-run- Preview code without modifying files--backup- Create backup before modifications
Examples
# Standard OIDC integration (generic)
/external-sso-login-integration \
--config-section ExternalSsoLogin \
--feature-flag Authentication.OpenIDConnect
# Custom configuration keys
/external-sso-login-integration \
--config-section OpenIdConnect \
--authority-key OidcAuthority \
--client-id-key ClientIdentifier \
--scopes "openid,profile,email,roles"
# With custom scopes (like NCell)
/external-sso-login-integration \
--custom-scopes "ncell_scheduler_stg,permissions" \
--auto-enable
# Preview only
/external-sso-login-integration --dry-run
Generated Code
The skill generates two code additions to your AuthServerModule.cs:
1. New Method: ConfigureExternalAuthentication()
private void ConfigureExternalAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var enableOpenIdConnect = IsFeatureEnabled(configuration, "Authentication.OpenIDConnect");
if (!enableOpenIdConnect)
{
Console.WriteLine("[SSO] OpenID Connect is disabled");
return;
}
Console.WriteLine("[SSO] Configuring OpenID Connect authentication");
// Read configuration (customize these keys to match your appsettings.json)
var authority = configuration["ExternalSsoLogin:Authority"]?.TrimEnd('/');
var clientId = configuration["ExternalSsoLogin:ClientId"];
var clientSecret = configuration["ExternalSsoLogin:ClientSecret"];
var bypassSSL = configuration.GetValue<bool>("ExternalSsoLogin:ByPassSSL", false);
var redirectUri = configuration["ExternalSsoLogin:RedirectUri"] ?? "/Account/Login";
if (string.IsNullOrWhiteSpace(authority))
throw new InvalidOperationException("ExternalSsoLogin:Authority is missing");
if (string.IsNullOrWhiteSpace(clientId))
throw new InvalidOperationException("ExternalSsoLogin:ClientId is missing");
var isDevOrBypass = hostingEnvironment.IsDevelopment() || bypassSSL;
Console.WriteLine($"[SSO] Authority: {authority}");
context.Services.AddAuthentication()
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = authority;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.UsePkce = true;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SignedOutRedirectUri = redirectUri;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("role");
// Add custom scopes here if needed
// options.Scope.Add("your_custom_scope");
// SSL bypass for dev/staging environments (DANGEROUS in production!)
if (isDevOrBypass)
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
options.Backchannel = new HttpClient(handler);
}
options.RequireHttpsMetadata = !isDevOrBypass;
// Validate issuer matches authority
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidIssuer = options.Authority,
};
// Proper logout handling
options.Events = new OpenIdConnectEvents
{
context =>
{
var result = await context.HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);
var idToken = result.Properties?.GetTokenValue(OpenIdConnectParameterNames.IdToken);
if (!string.IsNullOrEmpty(idToken))
{
context.ProtocolMessage.IdTokenHint = idToken;
}
}
};
});
if (hostingEnvironment.IsDevelopment())
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}
Console.WriteLine("[SSO] OpenID Connect configured successfully");
}
Key features:
- Feature flag controlled (disable/enable without recompiling)
- SSL validation bypass for development (
ByPassSSLflag) - PKCE enabled for security
- Token issuer validation
- Proper logout with id_token_hint
- Development PII logging for debugging
2. Update ConfigureServices() to Call New Method
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
// ... existing configuration code ...
ConfigureHealthChecks(context, configuration);
ConfigureRateLimiting(context, configuration);
ConfigureCookies(context, configuration);
ConfigureExternalAuthentication(context, configuration); // ← ADD THIS LINE
context.Services.AddTransient<ITokenManagementAppService, TokenManagementAppService>();
}
Customization Guide
Step 1: Update Configuration Keys
The generated code uses these config keys (customize with --config-section and related options):
var authority = configuration["ExternalSsoLogin:Authority"];
var clientId = configuration["ExternalSsoLogin:ClientId"];
var clientSecret = configuration["ExternalSsoLogin:ClientSecret"];
var bypassSSL = configuration.GetValue<bool>("ExternalSsoLogin:ByPassSSL", false);
var redirectUri = configuration["ExternalSsoLogin:RedirectUri"] ?? "/Account/Login";
To use custom keys, run the skill with options:
/external-sso-login-integration \
--config-section OpenIdConnect \
--authority-key OidcAuthority \
--client-id-key ClientId \
--client-secret-key ClientSecret \
--bypass-ssl-key AllowInvalidCertificates \
--redirect-uri-key PostLogoutRedirectUri
Would generate:
var authority = configuration["OpenIdConnect:OidcAuthority"];
var clientId = configuration["OpenIdConnect:ClientId"];
var clientSecret = configuration["OpenIdConnect:ClientSecret"];
var bypassSSL = configuration.GetValue<bool>("OpenIdConnect:AllowInvalidCertificates", false);
var redirectUri = configuration["OpenIdConnect:PostLogoutRedirectUri"] ?? "/Account/Login";
Step 2: Add Custom Scopes
Default scopes: openid,profile,email,role
To add custom scopes (like ncell_scheduler_stg):
Option A: Use --custom-scopes flag:
/external-sso-login-integration --custom-scopes "ncell_scheduler_stg,permissions"
Generates:
options.Scope.Add("ncell_scheduler_stg");
options.Scope.Add("permissions");
Option B: Manually edit after generation:
options.Scope.Add("ncell_scheduler_stg"); // ← Add your custom scope here
Step 3: Add Your appsettings.json Configuration
This is your responsibility. Add a section to your appsettings.json (or environment-specific config) with your OIDC provider details:
{
"ExternalSsoLogin": {
"Authority": "https://login.your-company.com/oauth2/default",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"ByPassSSL": false,
"RedirectUri": "/Account/Login"
},
"FeatureManagement": {
"FeatureFlags": [
{
"id": "Authentication.OpenIDConnect",
"enabled": false
}
]
}
}
For production:
- Use environment variables:
ExternalSsoLogin__ClientSecret=actual-secret - Use user secrets for development:
dotnet user-secrets set "ExternalSsoLogin:ClientSecret" "secret" - Use Azure Key Vault / HashiCorp Vault for production secrets
Step 4: Enable Feature Flag
Set "Authentication.OpenIDConnect" to true in your feature flags (appsettings.features.json or FeatureManagement table).
Implementation Details
Modified Files
The skill modifies only one file: {ProjectName}.AuthServer/{ProjectName}AuthServerModule.cs
If the file doesn't exist (your AuthServer has a different name), use --module flag:
/external-sso-login-integration --module src/MyApp/Auth/MyAuthModule.cs
Insertion Logic
The skill:
- Finds the
ConfigureServicesmethod - Inserts call to
ConfigureExternalAuthentication()at the end of existing configuration calls (beforeAddTransient<ITokenManagementAppService>or similar) - Adds the new method after
ConfigureRateLimiting()or at end of class (respecting existing method order) - Preserves existing code formatting and style
Dependencies
The generated code requires these namespaces (already present in typical AuthServer modules):
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Logging; // Optional (dev only)
If missing, the skill will add them at the top of the file.
Reusability Across Projects
Project A: Simple OIDC
# Generate code with default settings
/external-sso-login-integration --dry-run
# Review, then apply
/external-sso-login-integration --backup
Add to appsettings.json manually:
{
"ExternalSsoLogin": {
"Authority": "https://login.company-a.com/oauth2/default",
"ClientId": "company-a-client",
"ClientSecret": "..."
}
}
Project B: Different Config Keys
/external-sso-login-integration \
--config-section OpenIdConnect \
--authority-key IssuerUrl \
--client-id-key OidcClientId \
--client-secret-key OidcClientSecret \
--scopes "openid,profile,email" \
--backup
Add custom config:
{
"OpenIdConnect": {
"IssuerUrl": "https://auth.company-b.com/oauth",
"OidcClientId": "client-id",
"OidcClientSecret": "secret",
"AllowInvalidSsl": true
}
}
Code-Only Philosophy
This skill only generates C# code. Why?
- You control your configuration - Different projects have different config structures (some use
ExternalAuth:Google, others useSSO:Settings, etc.) - Security - You manage secrets (client secrets should never be in generated code samples)
- Flexibility - Use environment variables, Azure Key Vault, user secrets - your choice
- No assumptions - Your project might already have an
ExternalAuthsection with different keys - Cleaner git diffs - Code changes tracked separately from config changes
What You Need to Do After Running the Skill
- Review generated code (in
AuthServerModule.cs) - Add your configuration to
appsettings.json:- Choose your config section name
- Add
Authority,ClientId,ClientSecret,ByPassSSL,RedirectUrikeys
- Enable feature flag
Authentication.OpenIDConnect - Test the flow:
- Run AuthServer
- Go to
/Account/Login - You should see external login button
- Click → redirect to IdP → back to app
- Configure your IdP with correct redirect URI:
https://yourapp.com/signin-oidc
Configuration Flexibility
The generated code uses configuration["ExternalSsoLogin:Authority"] by default. But you can customize:
Example 1: Nested config
{
"Authentication": {
"External": {
"ProviderUrl": "...",
"AppClientId": "..."
}
}
}
Use flags:
/external-sso-login-integration \
--config-section "Authentication:External" \
--authority-key ProviderUrl \
--client-id-key AppClientId
Example 2: Environment variables only
export OIDC_AUTHORITY=https://login.company.com
export OIDC_CLIENT_ID=abc123
export OIDC_CLIENT_SECRET=secret
Code generated with:
/external-sso-login-integration \
--config-section "" \
--authority-key OIDC_AUTHORITY \
--client-id-key OIDC_CLIENT_ID \
--client-secret-key OIDC_CLIENT_SECRET
(NOTE: empty config section reads from root)
Troubleshooting
"Method already exists"
Cause: ConfigureExternalAuthentication() already defined in module.
Fix: Use --method-name to use different name, or manually merge code.
"Unable to find insertion point in ConfigureServices()"
Cause: Module structure different from expected pattern.
Fix: Use --insert-location with line number, or manually add method call.
"External login button not showing"
Cause: ABP UI needs external auth schemes registered before AddIdentity().
Fix: Ensure AddAuthentication().AddOpenIdConnect() is in ConfigureServices() before AddIdentity() (typical ABP pattern).
"No authentication handler for scheme 'OpenIdConnect'"
Cause: AddOpenIdConnect() not called (feature flag might be off).
Fix: Check logs for [SSO] OpenID Connect isdisabled. Enable feature flag.
Code Review Checklist
After running the skill, verify:
-
ConfigureExternalAuthentication()method added - Method signature matches:
private void ConfigureExternalAuthentication(ServiceConfigurationContext context, IConfiguration configuration) - Method called from
ConfigureServices()withconfigurationparameter -
IsFeatureEnabled(configuration, "Authentication.OpenIDConnect")check present - SSL bypass logic based on
ByPassSSLconfig +IsDevelopment() - Token validation sets
ValidIssuer = options.Authority - Logout event handler present (adds
IdTokenHint) -
options.UsePkce = true(security) -
options.GetClaimsFromUserInfoEndpoint = true(get full claims) - Scopes include at least
openid,profile,email - Development logging:
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true
Sample Output (Dry-Run)
🎯 External SSO Integration - Code Generation
📋 Configuration to add manually to appsettings.json:
{
"ExternalSsoLogin": {
"Authority": "https://your-idp.example.com/oauth2/default",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"ByPassSSL": false,
"RedirectUri": "/Account/Login"
}
}
🔧 Code modifications:
1. File: src/MyProject.AuthServer/MyProjectAuthServerModule.cs
ADD METHOD (after ConfigureRateLimiting or at end of class):
private void ConfigureExternalAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
var hostingEnvironment = context.Services.GetHostingEnvironment();
var enableOpenIdConnect = IsFeatureEnabled(configuration, "Authentication.OpenIDConnect");
if (!enableOpenIdConnect)
{
Console.WriteLine("[SSO] OpenID Connect is disabled");
return;
}
Console.WriteLine("[SSO] Configuring OpenID Connect authentication");
var authority = configuration["ExternalSsoLogin:Authority"]?.TrimEnd('/');
var clientId = configuration["ExternalSsoLogin:ClientId"];
var clientSecret = configuration["ExternalSsoLogin:ClientSecret"];
var bypassSSL = configuration.GetValue<bool>("ExternalSsoLogin:ByPassSSL", false);
var redirectUri = configuration["ExternalSsoLogin:RedirectUri"] ?? "/Account/Login";
if (string.IsNullOrWhiteSpace(authority))
throw new InvalidOperationException("ExternalSsoLogin:Authority is missing");
if (string.IsNullOrWhiteSpace(clientId))
throw new InvalidOperationException("ExternalSsoLogin:ClientId is missing");
var isDevOrBypass = hostingEnvironment.IsDevelopment() || bypassSSL;
Console.WriteLine($"[SSO] Authority: {authority}");
context.Services.AddAuthentication()
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = authority;
options.ClientId = clientId;
options.ClientSecret = clientSecret;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.UsePkce = true;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.SignedOutRedirectUri = redirectUri;
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("role");
if (isDevOrBypass)
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
options.Backchannel = new HttpClient(handler);
}
options.RequireHttpsMetadata = !isDevOrBypass;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidIssuer = options.Authority,
};
options.Events = new OpenIdConnectEvents
{
context =>
{
var result = await context.HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);
var idToken = result.Properties?.GetTokenValue(OpenIdConnectParameterNames.IdToken);
if (!string.IsNullOrEmpty(idToken))
{
context.ProtocolMessage.IdTokenHint = idToken;
}
}
};
});
if (hostingEnvironment.IsDevelopment())
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}
Console.WriteLine("[SSO] OpenID Connect configured successfully");
}
ADD METHOD CALL in ConfigureServices():
Find line containing: "ConfigureCookies(context, configuration);"
Add immediately after:
ConfigureExternalAuthentication(context, configuration);
2. Feature flag to enable (in appsettings.features.json or database):
{
"FeatureManagement": {
"FeatureFlags": [
{ "id": "Authentication.OpenIDConnect", "enabled": true }
]
}
}
⏱ Code generation: <1 second
💾 Backup: Not created (use --backup flag next time)
Complete Workflow Example
# 1. Generate code (dry-run to preview)
/external-sso-login-integration --dry-run
# 2. Apply with backup
/external-sso-login-integration --backup
# 3. Manually edit appsettings.json (or appsettings.Production.json):
# Add ExternalSsoLogin section with your IdP credentials
# 4. Enable feature flag:
# In appsettings.features.json add: "Authentication.OpenIDConnect": true
# OR in database: INSERT INTO AbpSettings WHERE Name = 'FeatureManagement:FeatureFlags'
# 5. Build and run
dotnet build src/MyProject.AuthServer/
dotnet run --project src/MyProject.AuthServer/
# 6. Check logs for: [SSO] OpenID Connect configured successfully
# 7. Test: Navigate to https://localhost:44333/Account/Login
# Should redirect to your IdP when clicking external login button
Limitations
- Single provider only - This pattern supports one OIDC provider per deployment. For multiple providers, you'd need to extend the code.
- No UI generation - Assumes you have ABP Account module with external login UI
- Assumes AuthServer module - If your project doesn't have separate AuthServer, adapt for HttpApi.Host
- ASP.NET Core Identity required - Uses
IdentityConstants.ExternalScheme
Adapting to Different Module Names
If your AuthServer module has a different name or structure:
# Specify module file explicitly
/external-sso-login-integration --module src/MyApp/Auth/MyCustomAuthModule.cs
The skill will analyze the file and insert code appropriately.
Security Best Practices
- Never commit client secrets - Use environment variables or secret managers
- Enable HTTPS - Set
ByPassSSL=falsein production - Validate issuer -
ValidIssuer = Authorityprevents token substitution attacks - Use PKCE - Already enabled (
UsePkce=true) - don't disable - Request minimal scopes - Only request
openid,profile,emailplus necessary custom scopes - Enable feature flag - Keep SSO disabled until ready to test
- Monitor logs - Watch for
[SSO]messages and errors
Skill Version: 2.0.0 (Code-Only, Reusable)
Last Updated: 2025-03-12
Compatible With: ABP Framework 8.0+, .NET 8.0+
Pattern Source: Adapted from ACMS AccessControlManagementSystemAuthServerModule
Reusability: ✅ Works with any ABP project (customize configuration keys via flags)