private static void ConfigureExternalAuthenticationServices(AuthenticationBuilder authenticationBuilder, TenantsBuilder tenantsBuilder, IConfiguration configuration) { if (tenantsBuilder != null) { authenticationBuilder.AddOpenIdConnect(IdentityCoreConstants.ExternalOidcScheme, openIdConnect => { // will be configured dynamically openIdConnect.ClientId = "N/A"; openIdConnect.Authority = "https://nowhere.nowhere"; }); tenantsBuilder.WithPerTenantOptions <OpenIdConnectOptions>((openIdConnect, tenantInfo) => { if (string.Equals(tenantInfo.ExternalAuthenticationMethod, TenantConstants.ExternalAuthenticationMethodOidc)) { var oidcClientId = tenantInfo.OidcClientId; var oidcClientSecret = tenantInfo.OidcClientSecret; var oidcEndpointUrl = tenantInfo.OidcEndpointUrl; openIdConnect.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; openIdConnect.SignOutScheme = IdentityServerConstants.SignoutScheme; openIdConnect.Authority = oidcEndpointUrl; openIdConnect.ClientId = oidcClientId; openIdConnect.ClientSecret = oidcClientSecret; // make sure we get user group membership information openIdConnect.GetClaimsFromUserInfoEndpoint = true; openIdConnect.Scope.Add("openid"); openIdConnect.Scope.Add("email"); openIdConnect.Scope.Add("phone"); openIdConnect.Scope.Add("profile"); } }); authenticationBuilder.AddSaml2(IdentityCoreConstants.ExternalSamlScheme, saml => { // will be configured dynamically }); tenantsBuilder.WithPerTenantOptions <Saml2Options>((saml, tenantInfo) => { if (string.Equals(tenantInfo.ExternalAuthenticationMethod, TenantConstants.ExternalAuthenticationMethodSaml)) { var samlEntityId = tenantInfo.SamlEntityId; var samlMetadataLocation = tenantInfo.SamlMetadataLocation; var samlProviderUrl = tenantInfo.SamlProviderUrl; var samlCertificate = tenantInfo.SamlCertificate; saml.SPOptions.EntityId = new EntityId(samlEntityId); if (tenantInfo.SamlAllowWeakSigningAlgorithm) { // OK this is less secure, but sometimes we're having trouble // e.g. with SSO Circle that still uses SHA1 saml.SPOptions.MinIncomingSigningAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; } saml.IdentityProviders.Add( new IdentityProvider(new EntityId(samlProviderUrl), saml.SPOptions) { LoadMetadata = true, MetadataLocation = samlMetadataLocation } ); if (samlCertificate != null) { saml.SPOptions.ServiceCertificates.Add(new ServiceCertificate() { Certificate = samlCertificate, Use = CertificateUse.Signing }); } saml.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; saml.SignOutScheme = IdentityServerConstants.SignoutScheme; } }); } }
private static void ConfigureJwtAuthentication(IServiceCollection services, TenantsBuilder tenantsBuilder, IConfiguration configuration) { var authenticationBuilder = services.AddAuthentication(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); string[] requiredScopesSplit = null; string requiredScopes = configuration["Identity:Jwt:RequiredScopes"]; if (!string.IsNullOrEmpty(requiredScopes)) { requiredScopesSplit = requiredScopes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); if (requiredScopesSplit.Length == 0) { requiredScopesSplit = null; } } if (tenantsBuilder == null) { string defaultClientAuthority = configuration[$"Identity:DefaultClient:Authority"]; if (string.IsNullOrEmpty(defaultClientAuthority)) { throw new Exception("Identity default client authority string is empty"); } string defaultClientAudience = configuration[$"Identity:DefaultClient:Audience"]; if (string.IsNullOrEmpty(defaultClientAudience)) { throw new Exception("Identity default client audience string is empty"); } authenticationBuilder.AddJwtBearer(IdentityCoreConstants.JwtScheme, jwt => { jwt.RequireHttpsMetadata = true; jwt.Authority = defaultClientAuthority; jwt.Audience = defaultClientAudience; jwt.TokenValidationParameters = new TokenValidationParameters() { ClockSkew = TimeSpan.FromMinutes(5), RequireSignedTokens = true, RequireExpirationTime = true, ValidateLifetime = true, // audience validation will be done via scope, as recommended in // https://github.com/IdentityServer/IdentityServer4/issues/127 ValidateAudience = false, ValidateIssuer = true, ValidIssuer = defaultClientAuthority }; }); services.AddAuthorization(options => { options.AddPolicy(IdentityCoreConstants.JwtPolicy, policy => { policy.AuthenticationSchemes.Add(IdentityCoreConstants.JwtScheme); if (requiredScopesSplit != null) { policy.RequireAssertion(handler => { return(CheckScopes(handler.User, requiredScopesSplit)); }); } policy.RequireAuthenticatedUser(); }); }); } else { authenticationBuilder.AddJwtBearer(IdentityCoreConstants.JwtScheme, jwt => { jwt.RequireHttpsMetadata = true; }); tenantsBuilder.WithPerTenantOptions <JwtBearerOptions>((jwt, tenantInfo) => { jwt.Authority = tenantInfo.DeveloperAuthority; jwt.Audience = tenantInfo.DeveloperAudience; var tokenValidationParameters = new TokenValidationParameters() { ClockSkew = TimeSpan.FromMinutes(5), RequireSignedTokens = true, RequireExpirationTime = true, ValidateLifetime = true, // audience validation will be done via scope, as recommended in // https://github.com/IdentityServer/IdentityServer4/issues/127 ValidateAudience = false, ValidateIssuer = true, ValidIssuer = tenantInfo.DeveloperAuthority }; if (tenantInfo.DeveloperCertificate != null) { // if we cannot resolve it from some discovery endpoint tokenValidationParameters.IssuerSigningKey = new X509SecurityKey(tenantInfo.DeveloperCertificate); } jwt.TokenValidationParameters = tokenValidationParameters; }); ConfigureExternalAuthenticationServices(authenticationBuilder, tenantsBuilder, configuration); services.AddAuthorization(options => { options.AddPolicy(IdentityCoreConstants.JwtPolicy, policy => { policy.AuthenticationSchemes.Add(IdentityCoreConstants.JwtScheme); policy.Requirements.Add(new ClientDeveloperUuidRequirement()); if (requiredScopesSplit != null) { policy.RequireAssertion(handler => { return(CheckScopes(handler.User, requiredScopesSplit)); }); } policy.RequireAuthenticatedUser(); }); }); services.AddSingleton <IAuthorizationHandler, ClientDeveloperUuidRequirementHandler>(); } }