Skip to content

Conversation

@mykeelium
Copy link
Contributor

@mykeelium mykeelium commented Dec 15, 2025

Description

The changes in this PR update the Authentication Logic to allow the extension of logic while validating a bearer token in the AuthMiddleware. In addition, some previously extended logic was unified into a struct that is now at the API layer rather than the Database layer.

Motivation and Context

Resolves BED-6900

How Has This Been Tested?

This has been tested by creating an instance and extending this logic here to be able to test the extended functionality in order to authenticate using AD FS.

Screenshots (optional):

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

Summary by CodeRabbit

Release Notes

  • New Features

    • Added bearer token validation support for improved authentication security.
    • Enhanced JWT token handling with standardized claims validation.
  • Bug Fixes

    • Improved error handling and logging for authentication failures.
  • Refactor

    • Streamlined authentication flow for better maintainability.

✏️ Tip: You can customize this high-level summary in your review settings.

@mykeelium mykeelium self-assigned this Dec 15, 2025
@mykeelium mykeelium added enhancement New feature or request api A pull request containing changes affecting the API code. labels Dec 15, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

Walkthrough

This PR refactors the authentication system by introducing an AuthExtensions interface to encapsulate token/claims handling, renaming the main authenticator type to AuthenticatorBase, migrating JWT claims from SessionData/StandardClaims to jwt.RegisteredClaims, introducing a new ValidateBearerToken method, and relocating login-related types from the api package to the model package.

Changes

Cohort / File(s) Summary
Core Authentication Refactoring
cmd/api/src/api/auth.go, cmd/api/src/api/auth_internal_test.go
Introduces AuthExtensions interface for token/claims processing. Renames authenticator to AuthenticatorBase, updates all method receivers accordingly. Replaces SessionData/StandardClaims with jwt.RegisteredClaims in JWT creation. Adds ValidateBearerToken method to Authenticator interface. Updates ValidateSession to accept claimsID string parameter and resolves session from database. Refactors login methods to use model.LoginRequest/LoginDetails. Introduces comprehensive test coverage for authExtensions functionality including error scenarios.
Middleware Integration
cmd/api/src/api/middleware/auth.go
Switches AuthBearer flow from ValidateSession to ValidateBearerToken. Adds structured logging via slog for token validation errors. Groups login timer constants into single const block.
Mock Implementations
cmd/api/src/api/mocks/authExtensions.go, cmd/api/src/api/mocks/authenticator.go
Adds generated MockAuthExtensions with support for InitContextFromClaims, InitContextFromToken, and ParseClaimsAndVerifySignature. Updates MockAuthenticator.LoginWithSecret signature to use model.LoginRequest/LoginDetails. Adds ValidateBearerToken mock method.
Login API and Tests
cmd/api/src/api/v2/auth/login.go, cmd/api/src/api/v2/auth/login_internal_test.go, cmd/api/src/api/v2/auth/login_test.go
Updates login methods to use model.LoginRequest, model.LoginDetails, and model.LoginResponse instead of api package types. Updates all test cases and mock expectations to reflect new types.
SAML Test Updates
cmd/api/src/api/v2/auth/saml_internal_test.go, cmd/api/src/api/v2/apitest/test.go
Updates NewAuthenticator call to pass apimocks.NewMockAuthExtensions instead of database context initializer mock.
Type Definitions
cmd/api/src/model/auth.go
Introduces three new public types: LoginRequest (login credentials), LoginDetails (user + session token), and LoginResponse (authentication result payload).
Legacy Type Cleanup
cmd/api/src/auth/model.go
Removes unused SessionData struct and related methods (SessionID, UserID). Removes unused imports (strconv, jwt/v4).
Database and Configuration
cmd/api/src/database/auth.go, cmd/api/src/config/config.go
Removes AuthContextInitializer interface and concrete implementation from database package. Adds GetRootURLHost() method to Configuration type.
Service Initialization
cmd/api/src/services/entrypoint.go
Updates authenticator construction to use api.NewAuthExtensions(cfg, db) instead of database.NewContextInitializer(db).

Sequence Diagram

sequenceDiagram
    participant Client
    participant Middleware
    participant Authenticator as AuthenticatorBase
    participant AuthExt as AuthExtensions
    participant JWT as JWT Parser
    participant DB as Database

    Client->>Middleware: HTTP Request with Bearer Token
    Middleware->>Authenticator: ValidateBearerToken(ctx, jwtToken)
    Authenticator->>AuthExt: ParseClaimsAndVerifySignature(ctx, jwtToken)
    AuthExt->>JWT: Parse & Verify Signature (HS256)
    JWT-->>AuthExt: RegisteredClaims (ID, Subject, ExpiresAt, etc.)
    AuthExt->>DB: Validate claims (expiration, ownership)
    DB-->>AuthExt: Session metadata
    AuthExt->>AuthExt: InitContextFromClaims(ctx, claims)
    AuthExt-->>Authenticator: auth.Context
    Authenticator-->>Middleware: auth.Context
    Middleware-->>Client: Authorized Request Proceeds
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

  • JWT claim and signature changes: Verify correctness of RegisteredClaims migration, HS256 signing implementation, and signature verification logic across creation and validation paths.
  • AuthExtensions interface integration: Ensure proper dependency injection, initialization, and usage throughout authentication flows; validate mock implementations match interface contracts.
  • Type migration completeness: Confirm all references to LoginRequest/LoginDetails/LoginResponse moved from api to model package; verify no missed conversions in tests or downstream code.
  • Bearer token validation flow: Review new ValidateBearerToken method, session resolution from database, expiration checks, and error handling for invalid/expired tokens.
  • Test coverage consistency: Verify all test mocks and setup helpers correctly instantiate AuthenticatorBase and authExtensions; check new Test_authExtensions test scenarios cover critical paths and error cases.

Suggested reviewers

  • stephanieslamb
  • kpowderly
  • bsheth711

Poem

🐰 A token refactored, extensions now guide,
Claims verified cleanly, with safety inside,
From api to model, the types migrate free,
AuthenticatorBase hops with newfound spree! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description includes all essential sections: clear description of changes, motivation (Resolves BED-6900), testing approach, type categorization, and a mostly-complete checklist with contributing prerequisites met and tests confirmed.
Title check ✅ Passed The title accurately summarizes the main refactoring effort: introducing AuthExtensions to support bearer token validation. It is specific, concise, and clearly identifies the primary change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mcuomo/BED-6900

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mykeelium mykeelium changed the title POC BED-6900 - AuthExtensions to Support Bearer Authentication Validation BED-6900 - AuthExtensions to Support Bearer Authentication Validation Dec 19, 2025
}
}

type LoginRequest struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to model layer due to a circular dependency with api+mock+test

@mykeelium mykeelium changed the title BED-6900 - AuthExtensions to Support Bearer Authentication Validation refactor: BED-6900 - AuthExtensions to Support Bearer Authentication Validation Dec 19, 2025
@mykeelium mykeelium marked this pull request as ready for review December 19, 2025 18:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/api/src/api/auth.go (1)

230-234: Bug: context.Background().Err() always returns nil.

When the context is cancelled, returning context.Background().Err() instead of ctx.Err() will always return nil, effectively swallowing the cancellation error.

Proposed fix
 	case <-ctx.Done():
-		return context.Background().Err()
+		return ctx.Err()
 	}
🧹 Nitpick comments (2)
cmd/api/src/api/auth_internal_test.go (1)

500-504: Consider using errors.Is for error comparisons.

Using require.Equal for error comparison works here since you're comparing the exact same error instance, but errors.Is is the idiomatic approach for error comparison and handles wrapped errors correctly.

require.ErrorIs(t, err, database.ErrNotFound)
cmd/api/src/api/auth.go (1)

518-535: Review the Owner nil-check logic for bearer token validation.

The logic at lines 524-531 uses authContext.Owner == nil to determine if the token was created by BloodHound (internal session) versus an external provider. While this works given the current InitContextFromClaims implementation returns an empty context, it relies on an implicit contract.

Consider adding a comment or using a more explicit mechanism (e.g., a flag in auth.Context) to make this distinction clearer for future maintainers.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f0b41b and 4098d04.

📒 Files selected for processing (15)
  • cmd/api/src/api/auth.go (10 hunks)
  • cmd/api/src/api/auth_internal_test.go (13 hunks)
  • cmd/api/src/api/middleware/auth.go (3 hunks)
  • cmd/api/src/api/mocks/authExtensions.go (1 hunks)
  • cmd/api/src/api/mocks/authenticator.go (2 hunks)
  • cmd/api/src/api/v2/apitest/test.go (2 hunks)
  • cmd/api/src/api/v2/auth/login.go (4 hunks)
  • cmd/api/src/api/v2/auth/login_internal_test.go (2 hunks)
  • cmd/api/src/api/v2/auth/login_test.go (1 hunks)
  • cmd/api/src/api/v2/auth/saml_internal_test.go (2 hunks)
  • cmd/api/src/auth/model.go (0 hunks)
  • cmd/api/src/config/config.go (1 hunks)
  • cmd/api/src/database/auth.go (0 hunks)
  • cmd/api/src/model/auth.go (1 hunks)
  • cmd/api/src/services/entrypoint.go (1 hunks)
💤 Files with no reviewable changes (2)
  • cmd/api/src/database/auth.go
  • cmd/api/src/auth/model.go
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-07-22T20:30:34.839Z
Learnt from: LawsonWillard
Repo: SpecterOps/BloodHound PR: 1700
File: cmd/api/src/api/v2/saved_queries_test.go:3182-3182
Timestamp: 2025-07-22T20:30:34.839Z
Learning: In Go table-driven tests in cmd/api/src/api/v2/saved_queries_test.go, subtest parallelization with t.Parallel() is acceptable when tests are self-contained, each creating their own mock controller (gomock.NewController(t)) and having isolated mock expectations without shared state between subtests.

Applied to files:

  • cmd/api/src/api/v2/auth/login_test.go
  • cmd/api/src/api/v2/auth/saml_internal_test.go
  • cmd/api/src/api/auth_internal_test.go
  • cmd/api/src/api/v2/apitest/test.go
📚 Learning: 2025-05-29T18:24:23.227Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1509
File: packages/go/stbernard/command/license/internal/cmd.go:49-51
Timestamp: 2025-05-29T18:24:23.227Z
Learning: In the stbernard package, use slog for all logging prints instead of fmt.Printf/fmt.Fprintf to maintain consistency with the codebase's logging standards.

Applied to files:

  • cmd/api/src/api/middleware/auth.go
📚 Learning: 2025-06-25T17:52:33.291Z
Learnt from: superlinkx
Repo: SpecterOps/BloodHound PR: 1606
File: cmd/api/src/analysis/azure/post.go:33-35
Timestamp: 2025-06-25T17:52:33.291Z
Learning: In BloodHound Go code, prefer using explicit slog type functions like slog.Any(), slog.String(), slog.Int(), etc. over simple key-value pairs for structured logging. This provides better type safety and makes key-value pairs more visually distinct. For error types, use slog.Any("key", err) or slog.String("key", err.Error()).

Applied to files:

  • cmd/api/src/api/middleware/auth.go
📚 Learning: 2025-08-28T16:43:43.961Z
Learnt from: mvlipka
Repo: SpecterOps/BloodHound PR: 1784
File: packages/go/openapi/doc/openapi.json:18008-18029
Timestamp: 2025-08-28T16:43:43.961Z
Learning: In SpecterOps/BloodHound, packages/go/openapi/doc/openapi.json is generated from YAML under packages/go/openapi/src/schemas; edits must be made to the YAML and then the spec regenerated.

Applied to files:

  • cmd/api/src/api/v2/apitest/test.go
🧬 Code graph analysis (11)
cmd/api/src/api/v2/auth/login_test.go (2)
cmd/api/src/model/auth.go (3)
  • LoginRequest (601-606)
  • LoginDetails (608-611)
  • User (431-451)
cmd/api/src/auth/model.go (1)
  • ProviderTypeSecret (35-35)
cmd/api/src/api/v2/auth/saml_internal_test.go (3)
cmd/api/src/api/auth.go (1)
  • NewAuthenticator (143-151)
cmd/api/src/config/config.go (1)
  • Configuration (137-170)
cmd/api/src/api/mocks/authExtensions.go (1)
  • NewMockAuthExtensions (51-55)
cmd/api/src/model/auth.go (2)
packages/go/graphschema/azure/azure.go (1)
  • User (41-41)
packages/go/graphschema/ad/ad.go (1)
  • User (29-29)
cmd/api/src/api/mocks/authenticator.go (1)
cmd/api/src/model/auth.go (2)
  • LoginRequest (601-606)
  • LoginDetails (608-611)
cmd/api/src/api/mocks/authExtensions.go (1)
cmd/api/src/model/auth.go (1)
  • AuthToken (147-156)
cmd/api/src/api/middleware/auth.go (4)
cmd/api/src/auth/model.go (1)
  • Context (160-164)
cmd/api/src/api/marshalling.go (1)
  • WriteErrorResponse (77-85)
cmd/api/src/api/error.go (1)
  • BuildErrorResponse (134-145)
cmd/api/src/ctx/ctx.go (1)
  • Get (75-85)
cmd/api/src/api/auth.go (3)
cmd/api/src/model/auth.go (4)
  • AuthToken (147-156)
  • UserSession (580-589)
  • AuthSecret (253-262)
  • User (431-451)
cmd/api/src/config/config.go (1)
  • Configuration (137-170)
cmd/api/src/database/db.go (2)
  • Database (71-190)
  • ErrNotFound (41-41)
cmd/api/src/api/auth_internal_test.go (6)
cmd/api/src/model/auth.go (2)
  • User (431-451)
  • LoginRequest (601-606)
cmd/api/src/auth/model.go (1)
  • Context (160-164)
cmd/api/src/api/auth.go (2)
  • AuthenticatorBase (135-141)
  • ErrInvalidAuth (51-51)
cmd/api/src/database/mocks/db.go (1)
  • NewMockDatabase (55-59)
cmd/api/src/api/mocks/authExtensions.go (2)
  • NewMockAuthExtensions (51-55)
  • MockAuthExtensions (39-43)
cmd/api/src/database/db.go (1)
  • ErrNotFound (41-41)
cmd/api/src/api/v2/apitest/test.go (4)
cmd/api/src/api/v2/auth/auth.go (1)
  • NewManagementResource (69-81)
cmd/api/src/auth/model.go (1)
  • NewAuthorizer (93-95)
cmd/api/src/api/auth.go (1)
  • NewAuthenticator (143-151)
cmd/api/src/api/mocks/authExtensions.go (1)
  • NewMockAuthExtensions (51-55)
cmd/api/src/services/entrypoint.go (1)
cmd/api/src/api/auth.go (2)
  • NewAuthenticator (143-151)
  • NewAuthExtensions (81-86)
cmd/api/src/api/v2/auth/login.go (2)
cmd/api/src/model/auth.go (2)
  • LoginRequest (601-606)
  • LoginResponse (613-617)
cmd/api/src/api/marshalling.go (1)
  • WriteBasicResponse (90-99)
🔇 Additional comments (25)
cmd/api/src/config/config.go (1)

368-370: LGTM!

The GetRootURLHost() method provides a clean utility to format the root URL as a host string with scheme and hostname. The implementation is straightforward and correct.

cmd/api/src/api/v2/apitest/test.go (2)

25-25: LGTM!

The import alias apimocks helps clearly distinguish API mocks from database mocks, improving code readability.


46-46: LGTM!

The update to use apimocks.NewMockAuthExtensions(mockCtrl) correctly aligns with the new AuthExtensions interface introduced in this PR.

cmd/api/src/api/middleware/auth.go (3)

21-21: LGTM!

The addition of log/slog supports structured logging in the bearer token validation path.


71-77: LGTM!

The updated bearer token validation flow correctly uses the new ValidateBearerToken method and includes proper error logging with context. The user-facing error message is appropriately generic to avoid leaking implementation details.


205-208: LGTM!

Consolidating the constants into a single block improves code organization without changing behavior.

cmd/api/src/api/v2/auth/login_test.go (1)

60-77: LGTM!

The migration from api.LoginRequest/api.LoginDetails to model.LoginRequest/model.LoginDetails is correctly applied throughout the test. The test logic remains intact, and mock expectations properly use the new types.

cmd/api/src/services/entrypoint.go (1)

118-118: LGTM!

The migration to api.NewAuthExtensions(cfg, connections.RDMS) correctly implements the new authentication architecture, moving context initialization from the database layer to the API layer for better extensibility.

cmd/api/src/api/v2/auth/login.go (2)

32-32: LGTM!

The addition of the model package import supports the migration of login-related types to a centralized location.


50-72: LGTM!

The migration of login types from api.LoginRequest/api.LoginResponse to model.LoginRequest/model.LoginResponse is applied consistently throughout the login flow. The function logic remains unchanged, with only type references updated.

cmd/api/src/api/v2/auth/saml_internal_test.go (2)

30-30: LGTM!

The import alias for API mocks improves code clarity when working with multiple mock packages.


57-57: LGTM!

The test correctly uses apimocks.NewMockAuthExtensions(mockCtrl) to align with the new AuthExtensions-based authentication flow.

cmd/api/src/model/auth.go (1)

601-617: LGTM!

The new login-related types are well-structured and follow Go conventions:

  • LoginRequest properly uses omitempty for optional fields (Secret, OTP)
  • LoginDetails serves as an internal type without JSON tags
  • LoginResponse provides the public API response structure

Centralizing these types in the model package promotes reusability and consistency across authentication flows.

cmd/api/src/api/mocks/authenticator.go (1)

17-18: LGTM - Generated mock correctly reflects interface changes.

The MockGen-generated code properly implements the updated Authenticator interface with model.LoginRequest/model.LoginDetails types and the new ValidateBearerToken method.

cmd/api/src/api/v2/auth/login_internal_test.go (2)

62-90: LGTM - Consistent type migration to model package.

The test correctly updates all LoginRequest and LoginDetails references from the api package to the model package, aligning with the relocated types. Mock expectations are properly configured for the updated interface.


205-212: LGTM - TestLoginSuccess properly updated.

The success test case correctly uses model.LoginRequest as input and expects model.LoginDetails with the appropriate model.AuthSecret structure in the return value.

cmd/api/src/api/mocks/authExtensions.go (1)

17-22: LGTM - New AuthExtensions mock correctly generated.

The MockGen-generated MockAuthExtensions properly implements the new AuthExtensions interface with all three methods: InitContextFromClaims, InitContextFromToken, and ParseClaimsAndVerifySignature.

cmd/api/src/api/auth_internal_test.go (3)

65-80: LGTM - setupRequest properly updated.

The helper function correctly returns model.LoginRequest to align with the relocated type.


134-146: LGTM - NewTestAuthenticator uses AuthenticatorBase with authExtensions.

The test helper correctly instantiates AuthenticatorBase with the new authExtensions field using apimocks.NewMockAuthExtensions.


446-643: LGTM - Comprehensive test coverage for authExtensions.

The new Test_authExtensions suite provides good coverage for all three methods of the authExtensions implementation:

  • InitContextFromClaims default behavior
  • InitContextFromToken with valid/invalid UserID and error scenarios
  • ParseClaimsAndVerifySignature with valid tokens, expired tokens, and invalid signatures

The parallel test execution pattern is correctly implemented with each subtest creating its own mock controller. Based on learnings, this is an acceptable pattern for self-contained table-driven tests.

cmd/api/src/api/auth.go (5)

70-86: LGTM - Clean AuthExtensions interface and implementation.

The new AuthExtensions interface provides a clear extension point for token/claims handling. The constructor properly initializes the struct with configuration and database dependencies.


88-104: Note: InitContextFromClaims returns empty context by design.

The InitContextFromClaims method returns an empty auth.Context which is used as an extension point. The calling code in ValidateBearerToken (lines 524-531) checks for nil Owner to determine if the token was created by BloodHound and falls back to session validation.

This design is intentional for AD FS and other external identity provider extensions, where custom implementations can populate the Owner from external claims.


106-122: LGTM - Proper JWT signature verification with error handling.

The ParseClaimsAndVerifySignature method correctly:

  • Parses JWT with claims using the configured signing key
  • Returns ErrInvalidAuth for signature errors
  • Returns the claims even on validation errors (useful for expired token handling)

537-584: LGTM - ValidateSession correctly handles session validation.

The refactored ValidateSession method properly:

  • Parses the claims ID from string to int64
  • Retrieves and validates the session from the database
  • Handles expired sessions, missing auth secrets, and EULA acceptance
  • Applies appropriate permission overrides for expired secrets

503-514: LGTM - JWT claims migration to RegisteredClaims.

The session JWT creation correctly uses jwt.RegisteredClaims with proper field mapping:

  • Issuer: Root URL host
  • ID: Session ID as string
  • Subject: User ID
  • IssuedAt/ExpiresAt: Proper jwt.NumericDate formatting

@mykeelium mykeelium changed the title refactor: BED-6900 - AuthExtensions to Support Bearer Authentication Validation refactor: AuthExtensions to Support Bearer Authentication Validation - BED-6900 Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api A pull request containing changes affecting the API code. enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants