From 99b95b5330d8942640921701279b554c695fbbc4 Mon Sep 17 00:00:00 2001
From: Chad Scharf <3904944+cscharf@users.noreply.github.com>
Date: Mon, 11 Jan 2021 11:03:46 -0500
Subject: [PATCH] Fix safari sso header size (#1065)
* Safari SSO header size fix - in progress
* Cleanup of memoryCacheTicketStore
* Redis cache ticket store + registration
* Revert some unecessary changes
* temp - distributed cookie: idsrv.external
* Ticket data cached storage added
* OIDC working w/ substantially reduced cookie size
* Added distributed cache cookie manager
* Removed hybrid OIDC flow
* Enable self-hosted folks to use Redis for SSO
* Also allow self-hosted to use Redis cont...
---
.../src/Sso/Controllers/AccountController.cs | 4 +-
bitwarden_license/src/Sso/Sso.csproj | 1 +
bitwarden_license/src/Sso/Startup.cs | 4 +-
.../DynamicAuthenticationSchemeProvider.cs | 14 +++-
src/Api/Startup.cs | 2 -
src/Core/Constants.cs | 5 ++
src/Core/Core.csproj | 1 +
src/Core/GlobalSettings.cs | 1 +
...onfigureOpenIdConnectDistributedOptions.cs | 53 ++++++++++++++
.../DistributedCacheCookieManager.cs | 70 ++++++++++++++++++
.../DistributedCacheTicketDataFormatter.cs | 66 +++++++++++++++++
.../IdentityServer/MemoryCacheTicketStore.cs | 56 ++++++++++++++
src/Core/IdentityServer/OidcIdentityClient.cs | 4 +-
.../IdentityServer/RedisCacheTicketStore.cs | 68 +++++++++++++++++
.../Utilities/ServiceCollectionExtensions.cs | 73 +++++++++++++------
src/Identity/Controllers/AccountController.cs | 6 +-
src/Identity/Startup.cs | 6 +-
17 files changed, 398 insertions(+), 36 deletions(-)
create mode 100644 src/Core/IdentityServer/ConfigureOpenIdConnectDistributedOptions.cs
create mode 100644 src/Core/IdentityServer/DistributedCacheCookieManager.cs
create mode 100644 src/Core/IdentityServer/DistributedCacheTicketDataFormatter.cs
create mode 100644 src/Core/IdentityServer/MemoryCacheTicketStore.cs
create mode 100644 src/Core/IdentityServer/RedisCacheTicketStore.cs
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
{