diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index ac11640f58..a627773f47 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -204,7 +204,7 @@ namespace Bit.Sso.Controllers { // Read external identity from the temporary cookie var result = await HttpContext.AuthenticateAsync( - IdentityServerConstants.ExternalCookieAuthenticationScheme); + Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); if (result?.Succeeded != true) { throw new Exception(_i18nService.T("ExternalAuthenticationError")); @@ -249,7 +249,7 @@ namespace Bit.Sso.Controllers } // Delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + await HttpContext.SignOutAsync(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); // Retrieve return URL var returnUrl = result.Properties.Items["return_url"] ?? "~/"; diff --git a/bitwarden_license/src/Sso/Sso.csproj b/bitwarden_license/src/Sso/Sso.csproj index b967907ede..6572802efd 100644 --- a/bitwarden_license/src/Sso/Sso.csproj +++ b/bitwarden_license/src/Sso/Sso.csproj @@ -7,6 +7,7 @@ bitwarden-Sso + diff --git a/bitwarden_license/src/Sso/Startup.cs b/bitwarden_license/src/Sso/Startup.cs index 906c3cbac3..ef130e0784 100644 --- a/bitwarden_license/src/Sso/Startup.cs +++ b/bitwarden_license/src/Sso/Startup.cs @@ -58,7 +58,9 @@ namespace Bit.Sso } // Authentication - services.AddAuthentication(); + services.AddDistributedIdentityServices(globalSettings); + services.AddAuthentication() + .AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); services.AddSsoServices(globalSettings); // IdentityServer diff --git a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs index 8dc2670ed6..ded86fde6e 100644 --- a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs +++ b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs @@ -14,8 +14,10 @@ using Bit.Sso.Models; using Bit.Sso.Utilities; using IdentityModel; using IdentityServer4; +using IdentityServer4.Infrastructure; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; @@ -40,6 +42,7 @@ namespace Bit.Core.Business.Sso private readonly Dictionary _cachedSchemes; private readonly Dictionary _cachedHandlerSchemes; private readonly SemaphoreSlim _semaphore; + private readonly IHttpContextAccessor _httpContextAccessor; private DateTime? _lastSchemeLoad; private IEnumerable _schemesCopy = Array.Empty(); @@ -54,7 +57,8 @@ namespace Bit.Core.Business.Sso ISsoConfigRepository ssoConfigRepository, ILogger logger, GlobalSettings globalSettings, - SamlEnvironment samlEnvironment) + SamlEnvironment samlEnvironment, + IHttpContextAccessor httpContextAccessor) : base(options) { _oidcPostConfigureOptions = oidcPostConfigureOptions; @@ -81,6 +85,7 @@ namespace Bit.Core.Business.Sso _cachedSchemes = new Dictionary(); _cachedHandlerSchemes = new Dictionary(); _semaphore = new SemaphoreSlim(1); + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); } private bool CacheIsValid @@ -304,7 +309,7 @@ namespace Bit.Core.Business.Sso ClientSecret = config.ClientSecret, ResponseType = "code", ResponseMode = "form_post", - SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme, + SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme, SignOutScheme = IdentityServerConstants.SignoutScheme, SaveTokens = false, // reduce overall request size TokenValidationParameters = new TokenValidationParameters @@ -332,6 +337,8 @@ namespace Bit.Core.Business.Sso oidcOptions.Scope.Add(OpenIdConnectScopes.Profile); } + oidcOptions.StateDataFormat = new DistributedCacheStateDataFormatter(_httpContextAccessor, name); + return new DynamicAuthenticationScheme(name, name, typeof(OpenIdConnectHandler), oidcOptions, SsoType.OpenIdConnect); } @@ -407,8 +414,9 @@ namespace Bit.Core.Business.Sso var options = new Saml2Options { SPOptions = spOptions, - SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme, + SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme, SignOutScheme = IdentityServerConstants.DefaultCookieAuthenticationScheme, + CookieManager = new IdentityServer.DistributedCacheCookieManager(), }; options.IdentityProviders.Add(idp); diff --git a/src/Api/Startup.cs b/src/Api/Startup.cs index ce214d3b0b..58e1661e34 100644 --- a/src/Api/Startup.cs +++ b/src/Api/Startup.cs @@ -33,8 +33,6 @@ namespace Bit.Api public void ConfigureServices(IServiceCollection services) { - var provider = services.BuildServiceProvider(); - // Options services.AddOptions(); diff --git a/src/Core/Constants.cs b/src/Core/Constants.cs index 31e04baa8c..71a8404f50 100644 --- a/src/Core/Constants.cs +++ b/src/Core/Constants.cs @@ -9,4 +9,9 @@ { public const string LinkSso = "LinkSso"; } + + public static class AuthenticationSchemes + { + public const string BitwardenExternalCookieAuthenticationScheme = "bw.external"; + } } diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index e438bb4943..30ef21eb74 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -58,6 +58,7 @@ + diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index 364c2b43ed..b5c2582332 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -141,6 +141,7 @@ namespace Bit.Core { public string CertificateThumbprint { get; set; } public string CertificatePassword { get; set; } + public string RedisConnectionString { get; set; } } public class DataProtectionSettings diff --git a/src/Core/IdentityServer/ConfigureOpenIdConnectDistributedOptions.cs b/src/Core/IdentityServer/ConfigureOpenIdConnectDistributedOptions.cs new file mode 100644 index 0000000000..ae10004fa8 --- /dev/null +++ b/src/Core/IdentityServer/ConfigureOpenIdConnectDistributedOptions.cs @@ -0,0 +1,53 @@ +using System; +using IdentityServer4.Configuration; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Redis; +using Microsoft.Extensions.Options; + +namespace Bit.Core.IdentityServer +{ + public class ConfigureOpenIdConnectDistributedOptions : IPostConfigureOptions + { + private readonly IdentityServerOptions _idsrv; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly GlobalSettings _globalSettings; + + public ConfigureOpenIdConnectDistributedOptions(IHttpContextAccessor httpContextAccessor, GlobalSettings globalSettings, + IdentityServerOptions idsrv) + { + _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + _globalSettings = globalSettings; + _idsrv = idsrv; + } + + public void PostConfigure(string name, CookieAuthenticationOptions options) + { + options.CookieManager = new DistributedCacheCookieManager(); + + if (name != AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme) + { + // Ignore + return; + } + + options.Cookie.Name = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme; + options.Cookie.IsEssential = true; + options.Cookie.SameSite = _idsrv.Authentication.CookieSameSiteMode; + options.TicketDataFormat = new DistributedCacheTicketDataFormatter(_httpContextAccessor, name); + + if (string.IsNullOrWhiteSpace(_globalSettings.IdentityServer?.RedisConnectionString)) + { + options.SessionStore = new MemoryCacheTicketStore(); + } + else + { + var redisOptions = new RedisCacheOptions + { + Configuration = _globalSettings.IdentityServer.RedisConnectionString, + }; + options.SessionStore = new RedisCacheTicketStore(redisOptions); + } + } + } +} diff --git a/src/Core/IdentityServer/DistributedCacheCookieManager.cs b/src/Core/IdentityServer/DistributedCacheCookieManager.cs new file mode 100644 index 0000000000..19b41cb8c1 --- /dev/null +++ b/src/Core/IdentityServer/DistributedCacheCookieManager.cs @@ -0,0 +1,70 @@ +using System; +using System.Text; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.IdentityServer +{ + public class DistributedCacheCookieManager : ICookieManager + { + private readonly ChunkingCookieManager _cookieManager; + + public DistributedCacheCookieManager() + { + _cookieManager = new ChunkingCookieManager(); + } + + private string CacheKeyPrefix => "cookie-data"; + + public void AppendResponseCookie(HttpContext context, string key, string value, CookieOptions options) + { + var id = Guid.NewGuid().ToString(); + var cacheKey = GetKey(key, id); + + var expiresUtc = options.Expires ?? DateTimeOffset.UtcNow.AddMinutes(15); + var cacheOptions = new DistributedCacheEntryOptions() + .SetAbsoluteExpiration(expiresUtc); + + var data = Encoding.UTF8.GetBytes(value); + + var cache = GetCache(context); + cache.Set(cacheKey, data, cacheOptions); + + // Write the cookie with the identifier as the body + _cookieManager.AppendResponseCookie(context, key, id, options); + } + + public void DeleteCookie(HttpContext context, string key, CookieOptions options) + { + _cookieManager.DeleteCookie(context, key, options); + var id = GetId(context, key); + if (!string.IsNullOrWhiteSpace(id)) + { + var cacheKey = GetKey(key, id); + GetCache(context).Remove(cacheKey); + } + } + + public string GetRequestCookie(HttpContext context, string key) + { + var id = GetId(context, key); + if (string.IsNullOrWhiteSpace(id)) + { + return null; + } + var cacheKey = GetKey(key, id); + return GetCache(context).GetString(cacheKey); + } + + private IDistributedCache GetCache(HttpContext context) => + context.RequestServices.GetRequiredService(); + + private string GetKey(string key, string id) => $"{CacheKeyPrefix}-{key}-{id}"; + + private string GetId(HttpContext context, string key) => + context.Request.Cookies.ContainsKey(key) ? + context.Request.Cookies[key] : null; + } +} diff --git a/src/Core/IdentityServer/DistributedCacheTicketDataFormatter.cs b/src/Core/IdentityServer/DistributedCacheTicketDataFormatter.cs new file mode 100644 index 0000000000..e25aaf092c --- /dev/null +++ b/src/Core/IdentityServer/DistributedCacheTicketDataFormatter.cs @@ -0,0 +1,66 @@ +using System; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; + +namespace Bit.Core.IdentityServer +{ + public class DistributedCacheTicketDataFormatter : ISecureDataFormat + { + private readonly IHttpContextAccessor _httpContext; + private readonly string _name; + + public DistributedCacheTicketDataFormatter(IHttpContextAccessor httpContext, string name) + { + _httpContext = httpContext; + _name = name; + } + + private string CacheKeyPrefix => "ticket-data"; + private IDistributedCache Cache => _httpContext.HttpContext.RequestServices.GetRequiredService(); + private IDataProtector Protector => _httpContext.HttpContext.RequestServices.GetRequiredService() + .CreateProtector(CacheKeyPrefix, _name); + + public string Protect(AuthenticationTicket data) => Protect(data, null); + public string Protect(AuthenticationTicket data, string purpose) + { + var key = Guid.NewGuid().ToString(); + var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}"; + + var expiresUtc = data.Properties.ExpiresUtc ?? + DateTimeOffset.UtcNow.AddMinutes(15); + + var options = new DistributedCacheEntryOptions(); + options.SetAbsoluteExpiration(expiresUtc); + + var ticket = TicketSerializer.Default.Serialize(data); + Cache.Set(cacheKey, ticket, options); + + return Protector.Protect(key); + } + + public AuthenticationTicket Unprotect(string protectedText) => Unprotect(protectedText, null); + public AuthenticationTicket Unprotect(string protectedText, string purpose) + { + if (string.IsNullOrWhiteSpace(protectedText)) + { + return null; + } + + // Decrypt the key and retrieve the data from the cache. + var key = Protector.Unprotect(protectedText); + var cacheKey = $"{CacheKeyPrefix}-{_name}-{purpose}-{key}"; + var ticket = Cache.Get(cacheKey); + + if (ticket == null) + { + return null; + } + + var data = TicketSerializer.Default.Deserialize(ticket); + return data; + } + } +} diff --git a/src/Core/IdentityServer/MemoryCacheTicketStore.cs b/src/Core/IdentityServer/MemoryCacheTicketStore.cs new file mode 100644 index 0000000000..7508ef25a2 --- /dev/null +++ b/src/Core/IdentityServer/MemoryCacheTicketStore.cs @@ -0,0 +1,56 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.Caching.Memory; + +namespace Bit.Core.IdentityServer +{ + public class MemoryCacheTicketStore : ITicketStore + { + private const string _keyPrefix = "auth-"; + private readonly IMemoryCache _cache; + + public MemoryCacheTicketStore() + { + _cache = new MemoryCache(new MemoryCacheOptions()); + } + + public async Task StoreAsync(AuthenticationTicket ticket) + { + var key = $"{_keyPrefix}{Guid.NewGuid()}"; + await RenewAsync(key, ticket); + return key; + } + + public Task RenewAsync(string key, AuthenticationTicket ticket) + { + var options = new MemoryCacheEntryOptions(); + var expiresUtc = ticket.Properties.ExpiresUtc; + if (expiresUtc.HasValue) + { + options.SetAbsoluteExpiration(expiresUtc.Value); + } + else + { + options.SetSlidingExpiration(TimeSpan.FromMinutes(15)); + } + + _cache.Set(key, ticket, options); + + return Task.FromResult(0); + } + + public Task RetrieveAsync(string key) + { + _cache.TryGetValue(key, out AuthenticationTicket ticket); + return Task.FromResult(ticket); + } + + public Task RemoveAsync(string key) + { + _cache.Remove(key); + return Task.FromResult(0); + } + } +} diff --git a/src/Core/IdentityServer/OidcIdentityClient.cs b/src/Core/IdentityServer/OidcIdentityClient.cs index 464b33df8f..4e0aa438ce 100644 --- a/src/Core/IdentityServer/OidcIdentityClient.cs +++ b/src/Core/IdentityServer/OidcIdentityClient.cs @@ -14,8 +14,8 @@ namespace Bit.Core.IdentityServer ClientSecrets = new List { new Secret(globalSettings.OidcIdentityClientKey.Sha256()) }; AllowedScopes = new string[] { - IdentityServerConstants.StandardScopes.OpenId, - IdentityServerConstants.StandardScopes.Profile + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile }; AllowedGrantTypes = GrantTypes.Code; Enabled = true; diff --git a/src/Core/IdentityServer/RedisCacheTicketStore.cs b/src/Core/IdentityServer/RedisCacheTicketStore.cs new file mode 100644 index 0000000000..65ae58c2d6 --- /dev/null +++ b/src/Core/IdentityServer/RedisCacheTicketStore.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Redis; + +namespace Bit.Core.IdentityServer +{ + public class RedisCacheTicketStore : ITicketStore + { + private const string _keyPrefix = "auth-"; + private readonly IDistributedCache _cache; + + public RedisCacheTicketStore(RedisCacheOptions options) + { + _cache = new RedisCache(options); + } + + public async Task StoreAsync(AuthenticationTicket ticket) + { + var key = $"{_keyPrefix}{Guid.NewGuid()}"; + await RenewAsync(key, ticket); + + return key; + } + + public Task RenewAsync(string key, AuthenticationTicket ticket) + { + var options = new DistributedCacheEntryOptions(); + var expiresUtc = ticket.Properties.ExpiresUtc ?? + DateTimeOffset.UtcNow.AddMinutes(15); + options.SetAbsoluteExpiration(expiresUtc); + + var val = SerializeToBytes(ticket); + _cache.Set(key, val, options); + + return Task.FromResult(0); + } + + public Task RetrieveAsync(string key) + { + AuthenticationTicket ticket; + var bytes = _cache.Get(key); + ticket = DeserializeFromBytes(bytes); + + return Task.FromResult(ticket); + } + + public Task RemoveAsync(string key) + { + _cache.Remove(key); + + return Task.FromResult(0); + } + + private static byte[] SerializeToBytes(AuthenticationTicket source) + { + return TicketSerializer.Default.Serialize(source); + } + + private static AuthenticationTicket DeserializeFromBytes(byte[] source) + { + return source == null ? null : TicketSerializer.Default.Deserialize(source); + } + } +} diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 2ddcdebcf1..c43a13861b 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -1,40 +1,43 @@ -using Bit.Core.Enums; +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Claims; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; +using AutoMapper; +using Bit.Core.Enums; using Bit.Core.Identity; using Bit.Core.IdentityServer; using Bit.Core.Models.Table; using Bit.Core.Repositories; +using Bit.Core.Resources; using Bit.Core.Services; +using Bit.Core.Utilities; using IdentityModel; +using IdentityServer4.AccessTokenValidation; +using IdentityServer4.Configuration; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.Localization; +using Microsoft.Azure.Storage; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.Http; -using System; -using System.IO; -using SqlServerRepos = Bit.Core.Repositories.SqlServer; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Serilog.Context; using EntityFrameworkRepos = Bit.Core.Repositories.EntityFramework; using NoopRepos = Bit.Core.Repositories.Noop; -using System.Threading.Tasks; +using SqlServerRepos = Bit.Core.Repositories.SqlServer; using TableStorageRepos = Bit.Core.Repositories.TableStorage; -using Microsoft.Extensions.DependencyInjection.Extensions; -using IdentityServer4.AccessTokenValidation; -using System.Security.Claims; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.HttpOverrides; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using Bit.Core.Utilities; -using Serilog.Context; -using AutoMapper; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Hosting; -using Microsoft.Azure.Storage; -using System.Reflection; -using Bit.Core.Resources; -using Microsoft.AspNetCore.Mvc.Localization; namespace Bit.Core.Utilities { @@ -502,5 +505,31 @@ namespace Bit.Core.Utilities return factory.Create("SharedResources", assemblyName.Name); }); } + + public static IServiceCollection AddDistributedIdentityServices(this IServiceCollection services, GlobalSettings globalSettings) + { + if (string.IsNullOrWhiteSpace(globalSettings.IdentityServer?.RedisConnectionString)) + { + services.AddDistributedMemoryCache(); + } + else + { + services.AddDistributedRedisCache(options => + options.Configuration = globalSettings.IdentityServer.RedisConnectionString); + } + + services.AddOidcStateDataFormatterCache(); + services.AddSession(); + services.ConfigureApplicationCookie(configure => configure.CookieManager = new DistributedCacheCookieManager()); + services.ConfigureExternalCookie(configure => configure.CookieManager = new DistributedCacheCookieManager()); + services.AddSingleton>( + svcs => new ConfigureOpenIdConnectDistributedOptions( + svcs.GetRequiredService(), + globalSettings, + svcs.GetRequiredService()) + ); + + return services; + } } } diff --git a/src/Identity/Controllers/AccountController.cs b/src/Identity/Controllers/AccountController.cs index ba1d3afa51..9e4e01b6f6 100644 --- a/src/Identity/Controllers/AccountController.cs +++ b/src/Identity/Controllers/AccountController.cs @@ -1,4 +1,4 @@ -using Bit.Core.Models.Api; +using Bit.Core.Models.Api; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Bit.Core.Services; @@ -153,7 +153,7 @@ namespace Bit.Identity.Controllers { // Read external identity from the temporary cookie var result = await HttpContext.AuthenticateAsync( - IdentityServerConstants.ExternalCookieAuthenticationScheme); + Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); if (result?.Succeeded != true) { throw new Exception("External authentication error"); @@ -190,7 +190,7 @@ namespace Bit.Identity.Controllers }, localSignInProps); // Delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + await HttpContext.SignOutAsync(Core.AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme); // Retrieve return URL var returnUrl = result.Properties.Items["return_url"] ?? "~/"; diff --git a/src/Identity/Startup.cs b/src/Identity/Startup.cs index 3295fa1ab5..f211c770a9 100644 --- a/src/Identity/Startup.cs +++ b/src/Identity/Startup.cs @@ -81,7 +81,9 @@ namespace Bit.Identity // Authentication services + .AddDistributedIdentityServices(globalSettings) .AddAuthentication() + .AddCookie(AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme) .AddOpenIdConnect("sso", "Single Sign On", options => { options.Authority = globalSettings.BaseServiceUri.InternalSso; @@ -91,8 +93,10 @@ namespace Bit.Identity options.ClientSecret = globalSettings.OidcIdentityClientKey; options.ResponseMode = "form_post"; - options.SignInScheme = IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme; + options.SignInScheme = AuthenticationSchemes.BitwardenExternalCookieAuthenticationScheme; options.ResponseType = "code"; + options.SaveTokens = false; + options.GetClaimsFromUserInfoEndpoint = true; options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents {