Repository: bitwarden/server
Pull request #5541: feat : matching request email to session email
Author: login: ike-kottlowski
Labels: name: DB-migrations-changed
Created: 2025-03-21T18:13:52Z | Merged: 2025-03-21T22:18:18Z

## 🎟️ Tracking

<!-- Paste the link to the Jira or GitHub issue or otherwise describe / point to where this change is coming from. -->

## 📔 Objective

<!-- Describe what the purpose of this PR is, for example what bug you're fixing or new feature you're adding. -->

## 📸 Screenshots

<!-- Required for any UI changes; delete if not applicable. Use fixed width images for better display. -->

## ⏰ Reminders before review

- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

## 🦮 Reviewer guidelines

<!-- Suggested interactions but feel free to use (or not) as you desire! -->

- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
- ⛏ (`:pick:`) for minor or nitpick changes



Diff:
diff --git a/src/Identity/IdentityServer/RequestValidators/AuthRequestHeaderValidator.cs b/src/Identity/IdentityServer/RequestValidators/AuthRequestHeaderValidator.cs
new file mode 100644
index 000000000000..5beb6e053710
--- /dev/null
+++ b/src/Identity/IdentityServer/RequestValidators/AuthRequestHeaderValidator.cs
@@ -0,0 +1,39 @@
+﻿using Bit.Core.Context;
+using Bit.Core.Utilities;
+
+namespace Bit.Identity.IdentityServer.RequestValidators;
+
+public class AuthRequestHeaderValidator : IAuthRequestHeaderValidator
+{
+    private readonly ICurrentContext _currentContext;
+
+    public AuthRequestHeaderValidator(ICurrentContext currentContext)
+    {
+        _currentContext = currentContext;
+    }
+
+    public bool ValidateAuthEmailHeader(string userEmail)
+    {
+        if (_currentContext.HttpContext.Request.Headers.TryGetValue("Auth-Email", out var authEmailHeader))
+        {
+            try
+            {
+                var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
+                if (authEmailDecoded != userEmail)
+                {
+                    return false;
+                }
+            }
+            catch (Exception e) when (e is InvalidOperationException || e is FormatException)
+            {
+                // Invalid B64 encoding
+                return false;
+            }
+        }
+        else
+        {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/Identity/IdentityServer/RequestValidators/IAuthRequestHeaderValidator.cs b/src/Identity/IdentityServer/RequestValidators/IAuthRequestHeaderValidator.cs
new file mode 100644
index 000000000000..80b6660aa1af
--- /dev/null
+++ b/src/Identity/IdentityServer/RequestValidators/IAuthRequestHeaderValidator.cs
@@ -0,0 +1,12 @@
+﻿namespace Bit.Identity.IdentityServer.RequestValidators;
+
+public interface IAuthRequestHeaderValidator
+{
+    /// <summary>
+    /// This method matches the Email in the header the input email. Implementation depends on
+    /// GrantValidator.
+    /// </summary>
+    /// <param name="userEmail">email fetched by grantValidator</param>
+    /// <returns>true if the emails match false otherwise</returns>
+    bool ValidateAuthEmailHeader(string userEmail);
+}
diff --git a/src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs b/src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
index 42f30eb82dd2..4e8c38b725a3 100644
--- a/src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
+++ b/src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
@@ -19,6 +19,7 @@ public class OpaqueKeyExchangeGrantValidator : BaseRequestValidator<ExtensionGra
     public const string GrantType = "opaque-ke";
     private readonly IOpaqueKeyExchangeService _opaqueKeyExchangeService;
     private readonly IFeatureService _featureService;
+    private readonly IAuthRequestHeaderValidator _authRequestHeaderValidator;
 
     public OpaqueKeyExchangeGrantValidator(
         UserManager<User> userManager,
@@ -28,15 +29,16 @@ public OpaqueKeyExchangeGrantValidator(
         ITwoFactorAuthenticationValidator twoFactorAuthenticationValidator,
         IOrganizationUserRepository organizationUserRepository,
         IMailService mailService,
-        ILogger<CustomTokenRequestValidator> logger,
+        ILogger<OpaqueKeyExchangeGrantValidator> logger,
         ICurrentContext currentContext,
         GlobalSettings globalSettings,
-        ISsoConfigRepository ssoConfigRepository,
         IUserRepository userRepository,
         IPolicyService policyService,
         IFeatureService featureService,
+        ISsoConfigRepository ssoConfigRepository,
         IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
-        IOpaqueKeyExchangeService opaqueKeyExchangeService)
+        IOpaqueKeyExchangeService opaqueKeyExchangeService,
+        IAuthRequestHeaderValidator authRequestHeaderValidator)
         : base(
             userManager,
             userService,
@@ -56,6 +58,7 @@ public OpaqueKeyExchangeGrantValidator(
     {
         _opaqueKeyExchangeService = opaqueKeyExchangeService;
         _featureService = featureService;
+        _authRequestHeaderValidator = authRequestHeaderValidator;
     }
 
     string IExtensionGrantValidator.GrantType => "opaque-ke";
@@ -81,10 +84,12 @@ public async Task ValidateAsync(ExtensionGrantValidationContext context)
             context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
             return;
         }
-
-        // TODO: we need to validate that the email sent up is the same one pulled from the session
-        // TODO: discuss with Ike if pulling over existing AuthEmailHeaderIsValid logic from
-        // ResourceOwnerPasswordValidator is best or if we should should refactor in some way.
+        if (_authRequestHeaderValidator.ValidateAuthEmailHeader(user.Email))
+        {
+            context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
+                "Auth-Email header invalid.");
+            return;
+        }
 
         await ValidateAsync(context, context.Request, new CustomValidatorRequestContext { User = user });
     }
diff --git a/src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs b/src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs
index 852bf27e408b..6e301cd5b775 100644
--- a/src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs
+++ b/src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs
@@ -23,6 +23,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
     private readonly ICaptchaValidationService _captchaValidationService;
     private readonly IAuthRequestRepository _authRequestRepository;
     private readonly IDeviceValidator _deviceValidator;
+    private readonly IAuthRequestHeaderValidator _authRequestHeaderValidator;
     public ResourceOwnerPasswordValidator(
         UserManager<User> userManager,
         IUserService userService,
@@ -40,7 +41,8 @@ public ResourceOwnerPasswordValidator(
         IPolicyService policyService,
         IFeatureService featureService,
         ISsoConfigRepository ssoConfigRepository,
-        IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder)
+        IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
+        IAuthRequestHeaderValidator authRequestHeaderValidator)
         : base(
             userManager,
             userService,
@@ -63,11 +65,12 @@ public ResourceOwnerPasswordValidator(
         _captchaValidationService = captchaValidationService;
         _authRequestRepository = authRequestRepository;
         _deviceValidator = deviceValidator;
+        _authRequestHeaderValidator = authRequestHeaderValidator;
     }
 
     public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
     {
-        if (!AuthEmailHeaderIsValid(context))
+        if (!_authRequestHeaderValidator.ValidateAuthEmailHeader(context.UserName))
         {
             context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant,
                 "Auth-Email header invalid.");
@@ -200,30 +203,4 @@ protected override ClaimsPrincipal GetSubject(ResourceOwnerPasswordValidationCon
     {
         return context.Result.Subject;
     }
-
-    private bool AuthEmailHeaderIsValid(ResourceOwnerPasswordValidationContext context)
-    {
-        if (_currentContext.HttpContext.Request.Headers.TryGetValue("Auth-Email", out var authEmailHeader))
-        {
-            try
-            {
-                var authEmailDecoded = CoreHelpers.Base64UrlDecodeString(authEmailHeader);
-                if (authEmailDecoded != context.UserName)
-                {
-                    return false;
-                }
-            }
-            catch (Exception e) when (e is InvalidOperationException || e is FormatException)
-            {
-                // Invalid B64 encoding
-                return false;
-            }
-        }
-        else
-        {
-            return false;
-        }
-
-        return true;
-    }
 }
diff --git a/src/Identity/Utilities/ServiceCollectionExtensions.cs b/src/Identity/Utilities/ServiceCollectionExtensions.cs
index d39d6b07f5d2..3858fda1b2e0 100644
--- a/src/Identity/Utilities/ServiceCollectionExtensions.cs
+++ b/src/Identity/Utilities/ServiceCollectionExtensions.cs
@@ -23,6 +23,7 @@ public static IIdentityServerBuilder AddCustomIdentityServerServices(this IServi
         services.AddTransient<IUserDecryptionOptionsBuilder, UserDecryptionOptionsBuilder>();
         services.AddTransient<IDeviceValidator, DeviceValidator>();
         services.AddTransient<ITwoFactorAuthenticationValidator, TwoFactorAuthenticationValidator>();
+        services.AddTransient<IAuthRequestHeaderValidator, AuthRequestHeaderValidator>();
 
         var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity);
         var identityServerBuilder = services
diff --git a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
index 3255d1044b23..66bdba29035d 100644
--- a/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
+++ b/src/SharedWeb/Utilities/ServiceCollectionExtensions.cs
@@ -110,7 +110,7 @@ public static SupportedDatabaseProviders AddDatabaseRepositories(this IServiceCo
     public static void AddBaseServices(this IServiceCollection services, IGlobalSettings globalSettings)
     {
         services.AddScoped<ICipherService, CipherService>();
-        services.AddSingleton<IOpaqueKeyExchangeService, OpaqueKeyExchangeService>();
+        services.AddScoped<IOpaqueKeyExchangeService, OpaqueKeyExchangeService>();
         services.AddUserServices(globalSettings);
         services.AddTrialInitiationServices();
         services.AddOrganizationServices(globalSettings);
diff --git a/src/Sql/Auth/dbo/Tables/OpaqueKeyExchangeCredential.sql b/src/Sql/Auth/dbo/Tables/OpaqueKeyExchangeCredential.sql
index 8e7f23f1242f..caacd157cfb3 100644
--- a/src/Sql/Auth/dbo/Tables/OpaqueKeyExchangeCredential.sql
+++ b/src/Sql/Auth/dbo/Tables/OpaqueKeyExchangeCredential.sql
@@ -8,7 +8,7 @@
     [EncryptedPrivateKey] VARCHAR(MAX) NOT NULL,
     [EncryptedUserKey] VARCHAR(MAX) NULL,
     [CreationDate] DATETIME2 (7) NOT NULL,
-    CONSTRAINT [PK_OpaqueKeyExchangeCredential] PRIMARY KEY CLUSTERED ([Id] ASC),
+    CONSTRAINT [PK_OpaqueKeyExchangeCredential] PRIMARY KEY CLUSTERED ([UserId]),
     CONSTRAINT [FK_OpaqueKeyExchangeCredential_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
 );
 
diff --git a/test/Identity.IntegrationTest/openid-configuration.json b/test/Identity.IntegrationTest/openid-configuration.json
index 4d74f66009d7..7d67f09f50b4 100644
--- a/test/Identity.IntegrationTest/openid-configuration.json
+++ b/test/Identity.IntegrationTest/openid-configuration.json
@@ -43,7 +43,8 @@
     "password",
     "urn:ietf:params:oauth:grant-type:device_code",
     "urn:openid:params:grant-type:ciba",
-    "webauthn"
+    "webauthn",
+    "opaque-ke"
   ],
   "response_types_supported": [
     "code",
diff --git a/util/Migrator/DbScripts/2025-03-20_00_CreateOpaqueKeyExchangeCredential.sql b/util/Migrator/DbScripts/2025-03-20_00_CreateOpaqueKeyExchangeCredential.sql
index 8b2850eeb289..4fbb5dcc699f 100644
--- a/util/Migrator/DbScripts/2025-03-20_00_CreateOpaqueKeyExchangeCredential.sql
+++ b/util/Migrator/DbScripts/2025-03-20_00_CreateOpaqueKeyExchangeCredential.sql
@@ -1,8 +1,3 @@
-
-
-
-
-
 IF OBJECT_ID('[dbo].[OpaqueKeyExchangeCredential]') IS NULL
 BEGIN
     CREATE TABLE [dbo].[OpaqueKeyExchangeCredential]
@@ -28,7 +23,6 @@ BEGIN
 END
 GO
 
-
 CREATE OR ALTER PROCEDURE [dbo].[OpaqueKeyExchangeCredential_Create]
     @Id UNIQUEIDENTIFIER OUTPUT,
     @UserId UNIQUEIDENTIFIER,


Reviews:
user: login: JaredSnider-Bitwarden
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:15:56Z
user: login: JaredSnider-Bitwarden
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:16:54Z
user: login: ike-kottlowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:22:20Z
user: login: ike-kottlowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:46:23Z
user: login: ike-kottlowski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T18:47:42Z
user: login: JaredSnider-Bitwarden
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:06:43Z
user: login: JaredSnider-Bitwarden
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T19:07:14Z

Review comments:
diff_hunk: @@ -76,16 +81,13 @@ public async Task ValidateAsync(ExtensionGrantValidationContext context)
         }
 
         var user = await _opaqueKeyExchangeService.GetUserForAuthenticatedSession(Guid.Parse(sessionId));
-        if (user == null)
+        if (user == null || !AuthEmailHeaderIsValid(user))
path: src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
user: login: JaredSnider-Bitwarden
body: We should have a different return for user == null, right? 
created_at: 2025-03-21T18:15:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,4 +150,34 @@ protected override void SetValidationErrorResult(
             CustomResponse = requestContext.CustomResponse
         };
     }
+
+    /// <summary>
+    /// This method matches the Email in the header to the email fetched from the SessionId
+    /// </summary>
+    /// <param name="user">User associated with the Authenticated cache</param>
+    /// <returns>true if the emails match false otherwise</returns>
+    private bool AuthEmailHeaderIsValid(User user)
path: src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
user: login: JaredSnider-Bitwarden
body: Now that we use this in 2 places, should it go on the base? 
created_at: 2025-03-21T18:16:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,4 +150,34 @@ protected override void SetValidationErrorResult(
             CustomResponse = requestContext.CustomResponse
         };
     }
+
+    /// <summary>
+    /// This method matches the Email in the header to the email fetched from the SessionId
+    /// </summary>
+    /// <param name="user">User associated with the Authenticated cache</param>
+    /// <returns>true if the emails match false otherwise</returns>
+    private bool AuthEmailHeaderIsValid(User user)
path: src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
user: login: ike-kottlowski
body: I'd rather create a new class `AuthHeaderValidator` that can be used to validate headers, so we can inject it where we need it rather than bloating the `BaseRequestValidator`. Easier to test and reduces dependency on the `BaseRequestValidator`.
created_at: 2025-03-21T18:22:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -76,16 +81,13 @@ public async Task ValidateAsync(ExtensionGrantValidationContext context)
         }
 
         var user = await _opaqueKeyExchangeService.GetUserForAuthenticatedSession(Guid.Parse(sessionId));
-        if (user == null)
+        if (user == null || !AuthEmailHeaderIsValid(user))
path: src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
user: login: ike-kottlowski
body: [Updated](https://github.com/bitwarden/server/pull/5541/commits/58b48e1532cefd52bab6d954d258c7906b1f8221)
created_at: 2025-03-21T18:46:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,4 +150,34 @@ protected override void SetValidationErrorResult(
             CustomResponse = requestContext.CustomResponse
         };
     }
+
+    /// <summary>
+    /// This method matches the Email in the header to the email fetched from the SessionId
+    /// </summary>
+    /// <param name="user">User associated with the Authenticated cache</param>
+    /// <returns>true if the emails match false otherwise</returns>
+    private bool AuthEmailHeaderIsValid(User user)
path: src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
user: login: ike-kottlowski
body: [Updated](https://github.com/bitwarden/server/pull/5541/commits/58b48e1532cefd52bab6d954d258c7906b1f8221)

I'm also open to other ideas, this is just the one that was most familiar. This does allow us to Validate other header values in the future if we want.
created_at: 2025-03-21T18:47:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -148,4 +150,34 @@ protected override void SetValidationErrorResult(
             CustomResponse = requestContext.CustomResponse
         };
     }
+
+    /// <summary>
+    /// This method matches the Email in the header to the email fetched from the SessionId
+    /// </summary>
+    /// <param name="user">User associated with the Authenticated cache</param>
+    /// <returns>true if the emails match false otherwise</returns>
+    private bool AuthEmailHeaderIsValid(User user)
path: src/Identity/IdentityServer/RequestValidators/OpaqueKeyExchangeGrantValidator.cs
user: login: JaredSnider-Bitwarden
body: Love it!
created_at: 2025-03-21T19:06:43Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-21T18:22:19Z
body: ![Logo](https://cdn.ast.checkmarx.net/integrations/logo/Checkmarx.png)
**Checkmarx One – Scan Summary & Details** – [82ff9dc4\-5168\-4fcf\-9c9c\-6c4222547b9d](https://ast.checkmarx.net/projects/fdc4f159-5309-4fab-8961-0f78c088bd72/scans?id=82ff9dc4-5168-4fcf-9c9c-6c4222547b9d&branch=innovation%2Fopaque-validate-email-in-grant-validator)
<!--fix issues section--><details><summary><b>Fixed Issues (1)</b></summary>
Great job! The following issues were fixed in this Pull Request

Severity|Issue|Source File / Package
---|---|---
![LOW](https://cdn.ast.checkmarx.net/integrations/severity/Low.png)|~~[Missing_CSP_Header](https://ast.checkmarx.net/results/fdc4f159-5309-4fab-8961-0f78c088bd72/82ff9dc4-5168-4fcf-9c9c-6c4222547b9d/sast/description/346/729519850006803664)~~|/src/Core/MailTemplates/Handlebars/AdminConsole/OrganizationUserRevokedForSingleOrgPolicy\.html\.hbs: [5](https://github.com/bitwarden/server/blob/innovation/opaque-validate-email-in-grant-validator//src/Core/MailTemplates/Handlebars/AdminConsole/OrganizationUserRevokedForSingleOrgPolicy.html.hbs#L5)
</details>

<!--fix issues section-->
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-21T22:16:14Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=bitwarden_server&pullRequest=5541) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=bitwarden_server&pullRequest=5541&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=bitwarden_server&pullRequest=5541&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=bitwarden_server&pullRequest=5541&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=bitwarden_server&pullRequest=5541&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=bitwarden_server&pullRequest=5541&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=bitwarden_server&pullRequest=5541)


author_association: NONE
user: login: codecov[bot]
created_at: 2025-03-21T22:21:09Z
body: ## [Codecov](https://app.codecov.io/gh/bitwarden/server/pull/5541?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) Report
Attention: Patch coverage is `83.87097%` with `5 lines` in your changes missing coverage. Please review.
> Please [upload](https://docs.codecov.com/docs/codecov-uploader) report for BASE (`innovation/opaque@6d4d7c7`). [Learn more](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#section-missing-base-commit) about missing BASE report.

| [Files with missing lines](https://app.codecov.io/gh/bitwarden/server/pull/5541?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) | Patch % | Lines |
|---|---|---|
| [...questValidators/OpaqueKeyExchangeGrantValidator.cs](https://app.codecov.io/gh/bitwarden/server/pull/5541?src=pr&el=tree&filepath=src%2FIdentity%2FIdentityServer%2FRequestValidators%2FOpaqueKeyExchangeGrantValidator.cs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden#diff-c3JjL0lkZW50aXR5L0lkZW50aXR5U2VydmVyL1JlcXVlc3RWYWxpZGF0b3JzL09wYXF1ZUtleUV4Y2hhbmdlR3JhbnRWYWxpZGF0b3IuY3M=) | 16.66% | [5 Missing :warning: ](https://app.codecov.io/gh/bitwarden/server/pull/5541?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden) |

<details><summary>Additional details and impacted files</summary>


```diff
@@                 Coverage Diff                  @@
##             innovation/opaque    #5541   +/-   ##
====================================================
  Coverage                     ?   44.32%           
====================================================
  Files                        ?     1560           
  Lines                        ?    71282           
  Branches                     ?     6367           
====================================================
  Hits                         ?    31598           
  Misses                       ?    38327           
  Partials                     ?     1357           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/bitwarden/server/pull/5541?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=bitwarden).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE