Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat jwe #140

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open

Feat jwe #140

wants to merge 34 commits into from

Conversation

james-d-elliott
Copy link
Member

No description provided.

@james-d-elliott james-d-elliott requested a review from a team as a code owner September 27, 2024 06:43
Copy link

coderabbitai bot commented Sep 27, 2024

Important

Review skipped

More than 25% of the files skipped due to max files limit. The review is being skipped to prevent a low-quality review.

32 files out of 114 files are above the max files limit of 75. Please upgrade to Pro plan to get higher limits.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request introduces significant updates to the Authelia OAuth 2.0 Framework, including a change in the module path, a new minimum Go version requirement, and various enhancements to the JWT handling and claims management. Key modifications include the renaming of the OAuth2Provider interface to Provider, the addition of new methods for retrieving and validating claims, and the introduction of a new utility file for JWT management. The changes also involve dependency updates and the implementation of new features related to OAuth 2.0 standards.

Changes

File Path Change Summary
README.md Updated module path to authelia.com/provider/oauth2, minimum Go version to 1.21, marked features as implemented, and detailed testing practices overhaul. Renamed OAuth2Provider to Provider. Various dependency updates.
token/jwt/claims_id_token.go Modified IDTokenClaims struct fields to use pointers, added new retrieval and validation methods, updated Valid method for enhanced checks, and implemented JSON unmarshalling.
token/jwt/claims_map.go Added methods for retrieving and verifying claims, refactored Valid method, and improved JSON unmarshalling. Introduced new helper methods for claim processing.
token/jwt/util.go Introduced functions for JWT validation, header validation, error handling, and key retrieval. Added utility functions for creating and managing JWKs and encoding JWTs.

Possibly related PRs


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 9915614 and 177e46f.

📒 Files selected for processing (1)
  • authorize_request_handler.go (5 hunks)
🔇 Additional comments (10)
authorize_request_handler.go (10)

12-12: Importing the "time" package is appropriate

The "time" package is correctly imported and utilized for time-related functions in the code.


69-70: Variables alg and algAny are properly declared

The variables alg and algAny are declared to handle the request object signing algorithm logic effectively.


73-75: Using assignment within the switch statement

Assigning alg within the switch statement is acceptable and enhances code conciseness.


119-119: Retrieving the issuer from configuration

The issuer is obtained using f.Config.GetIDTokenIssuer(ctx), which is necessary for subsequent validations.


128-134: Header validation options are correctly configured

The JWT header validation options ensure that the token headers match the client's expected values, enhancing security.


136-138: Proper validation of the JWT token headers

The token.Valid(optsValidHeader...) call effectively validates the token headers, and errors are appropriately handled.


140-144: Appropriate handling of unexpected 'none' algorithm usage

The code correctly returns an error when an unregistered client attempts to use the 'none' algorithm, ensuring compliance with security policies.


193-195: Validation of issuer configuration

Checking for an empty issuer and returning an error maintains the integrity of the authorization server configuration.


197-203: Claims validation options are properly set

The claims validation includes time functions, issuer, and audience checks, which are essential for accurate JWT validation.


205-206: Correct validation of JWT claims

Validating the JWT claims using claims.Valid(optsValidClaims...) ensures the token's integrity and authenticity.

authorize_request_handler.go Outdated Show resolved Hide resolved
authorize_request_handler.go Show resolved Hide resolved
Copy link

@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: 5

🧹 Outside diff range and nitpick comments (7)
internal/test_helpers.go (1)

Line range hint 1-74: Summary of changes and recommendations

The changes in this file are part of a larger refactoring effort to use an internal JWT library. While these changes simplify the code, they also introduce potential security and behavior concerns that need to be addressed:

  1. The switch to an internal JWT library (authelia.com/provider/oauth2/token/jwt) may have wide-ranging effects across the codebase. Ensure that all JWT-related code has been updated consistently.

  2. The use of UnsafeParseSignedAny in the ExtractJwtExpClaim function skips signature verification. While this might be acceptable for a test helper, it's crucial to ensure that this doesn't lead to any security vulnerabilities if used in non-test code.

  3. The change in checking the expiration claim (from nil to IsZero()) might affect behavior in edge cases. Verify that this change doesn't introduce any regressions in test cases.

  4. The error handling in the ExtractJwtExpClaim function has been simplified, which could potentially mask certain types of parsing errors. Consider adding more granular error handling to catch and log specific parsing issues.

Please review these points carefully and make any necessary adjustments to ensure the security and correctness of the JWT handling throughout the codebase.

token/jwt/claims_jwt.go (2)

229-239: Approve changes with a minor suggestion

The modifications to the toTime function improve type handling and error checking. The use of the new toInt64 helper function allows for handling more input types, and the conversion to UTC ensures consistent time representation.

Consider adding a check for zero value to improve robustness:

 func toTime(v any, def time.Time) (t time.Time, ok bool) {
     t = def

     var value int64

     if value, ok = toInt64(v); ok {
+        if value == 0 {
+            return def, true
+        }
         t = time.Unix(value, 0).UTC()
     }

     return
 }

This change would ensure that a zero value doesn't overwrite the default time unnecessarily.


241-263: Approve changes with a suggestion for improved readability

The new toInt64 function provides robust type conversion for various numeric types, with comprehensive error handling for json.Number. The function correctly returns false for unsupported types, which is a good practice.

Consider using a type switch for improved readability:

 func toInt64(v any) (val int64, ok bool) {
     var err error

-    switch t := v.(type) {
-    case float64:
-        return int64(t), true
-    case int64:
-        return t, true
-    case json.Number:
+    switch t := v.(type) {
+    case float64:
+        return int64(t), true
+    case int64:
+        return t, true
+    case json.Number:
         if val, err = t.Int64(); err == nil {
             return val, true
         }

         var valf float64

         if valf, err = t.Float64(); err != nil {
             return 0, false
         }

         return int64(valf), true
+    default:
+        return 0, false
     }
-
-    return 0, false
 }

This change makes the function more idiomatic Go and slightly more efficient by eliminating the need for a separate return statement at the end.

token/jwt/claims_map.go (4)

77-80: LGTM with a suggestion: GetAudience implementation is concise.

The GetAudience method correctly retrieves the 'aud' claim using a helper function StringSliceFromMap. This is good for code reuse. However, it would be helpful to include or document the StringSliceFromMap function for better understanding of the full behavior.

Consider adding a comment explaining the behavior of StringSliceFromMap or including its implementation in this file if it's not used elsewhere.


171-233: LGTM with a suggestion: Valid implementation is comprehensive and flexible.

The Valid method provides a thorough validation of multiple claims based on provided options. It correctly uses the verification methods implemented earlier and accumulates errors in a ValidationError struct, which is a good practice. The use of ClaimValidationOptions allows for flexible and customizable validation.

Consider adding a comment explaining the purpose of the ValidationError struct and how it's used to accumulate errors. This would improve code readability and make it easier for other developers to understand the error handling mechanism.


257-259: LGTM with a suggestion: toInt64 implementation is concise.

The toInt64 method correctly delegates the conversion to a helper function. This approach promotes code reuse and consistency.

Consider adding a comment explaining the behavior of the toInt64 helper function or including its implementation in this file if it's not used elsewhere. This would improve code readability and make it easier for other developers to understand the conversion logic.


Line range hint 1-392: Overall assessment: Significant improvements to JWT claim handling and validation.

This PR introduces substantial enhancements to the MapClaims type and associated functions:

  1. New methods for individual claim retrieval and validation have been added, providing more granular control.
  2. A flexible claim validation system has been implemented using the functional options pattern.
  3. Security has been improved with the use of constant-time comparisons for sensitive operations.
  4. The code maintains consistency in patterns and naming conventions throughout the file.

These changes significantly improve the robustness and flexibility of JWT claim handling and validation in the system. The new functionality allows for more precise control over claim verification, which can enhance security and reduce the likelihood of improper token acceptance.

Consider documenting the new claim validation system in the package documentation, including examples of how to use the new Valid method with various options. This will help other developers understand and correctly use the new functionality.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 177e46f and 3050236.

⛔ Files ignored due to path filters (2)
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (9)
  • handler/openid/flow_hybrid_test.go (4 hunks)
  • internal/test_helpers.go (2 hunks)
  • introspection_request_handler_test.go (0 hunks)
  • token/jwt/claims_id_token.go (2 hunks)
  • token/jwt/claims_jarm.go (1 hunks)
  • token/jwt/claims_jwt.go (3 hunks)
  • token/jwt/claims_map.go (2 hunks)
  • token/jwt/jwt_strategy_opts.go (1 hunks)
  • token/jwt/util.go (1 hunks)
💤 Files not reviewed due to no reviewable changes (1)
  • introspection_request_handler_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • handler/openid/flow_hybrid_test.go
  • token/jwt/jwt_strategy_opts.go
  • token/jwt/util.go
🔇 Additional comments (17)
internal/test_helpers.go (1)

21-21: Verify the impact of the JWT package change across the codebase.

The JWT package import has been changed from an external library to an internal one. This change could have a significant impact on JWT-related operations throughout the codebase.

Run the following script to check for any remaining usages of the old JWT package and potential inconsistencies:

Ensure that all JWT-related code has been updated to use the new internal package consistently.

✅ Verification successful

JWT package import successfully updated to the internal package across the codebase. No remaining references to the old package were found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usages of the old JWT package and potential inconsistencies.

# Test 1: Search for any remaining imports of the old JWT package
echo "Checking for any remaining imports of github.com/golang-jwt/jwt/v5:"
rg --type go 'github.com/golang-jwt/jwt/v5'

# Test 2: Search for any uses of the old JWT package types or functions
echo "Checking for any uses of types or functions from github.com/golang-jwt/jwt/v5:"
rg --type go 'jwt\.(NewWithClaims|Parse|ParseWithClaims|SigningMethodHS256|MapClaims|StandardClaims)'

# Test 3: Check for consistency in JWT package usage
echo "Checking for consistency in JWT package usage:"
rg --type go 'authelia.com/provider/oauth2/token/jwt'

Length of output: 15947

token/jwt/claims_jwt.go (1)

7-7: LGTM: Import change is appropriate

The addition of the "encoding/json" import is necessary and correctly implemented to support the handling of json.Number in the new toInt64 function.

token/jwt/claims_map.go (15)

27-38: LGTM: GetIssuer implementation is correct and robust.

The GetIssuer method correctly retrieves the 'iss' claim, handling both the case where the claim is not present and where it's not a string. This implementation follows good practices for working with map claims.


40-50: LGTM: VerifyIssuer implementation is correct and consistent.

The VerifyIssuer method correctly verifies the 'iss' claim, handling the required flag appropriately. It reuses the GetIssuer method, which ensures consistency in how the claim is retrieved and processed.


52-63: LGTM: GetSubject implementation is correct and consistent.

The GetSubject method correctly retrieves the 'sub' claim, following the same robust pattern as GetIssuer. This consistency in implementation across different claims is a good practice.


65-75: LGTM: VerifySubject implementation is correct and consistent.

The VerifySubject method correctly verifies the 'sub' claim, following the same pattern as VerifyIssuer. This consistency in implementation across different claim verifications is commendable.


84-92: LGTM: VerifyAudience implementation is correct and consistent.

The VerifyAudience method correctly verifies the 'aud' claim, handling the required flag appropriately. It reuses the GetAudience method and uses a helper function verifyAud, which is consistent with the pattern used in other verification methods.


94-105: LGTM: VerifyAudienceAll implementation is correct and consistent.

The VerifyAudienceAll method correctly verifies all provided audience values against the 'aud' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, which is good for consistency.


107-118: LGTM: VerifyAudienceAny implementation is correct and consistent.

The VerifyAudienceAny method correctly verifies if any of the provided audience values match the 'aud' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, maintaining consistency throughout the codebase.


120-123: LGTM: GetExpiresAt implementation is concise and consistent.

The GetExpiresAt method correctly retrieves the 'exp' claim using the helper method toInt64. This approach is consistent with other getter methods and promotes code reuse.


127-135: LGTM: VerifyExpiresAt implementation is correct and consistent.

The VerifyExpiresAt method correctly verifies the 'exp' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, maintaining consistency throughout the codebase.


137-140: LGTM: GetIssuedAt implementation is concise and consistent.

The GetIssuedAt method correctly retrieves the 'iat' claim using the helper method toInt64. This approach is consistent with other getter methods and promotes code reuse.


144-151: LGTM: VerifyIssuedAt implementation is correct and consistent.

The VerifyIssuedAt method correctly verifies the 'iat' claim, handling the required flag appropriately. It follows the same pattern as other verification methods, maintaining consistency throughout the codebase.


154-157: LGTM: GetNotBefore implementation is concise and consistent.

The GetNotBefore method correctly retrieves the 'nbf' claim using the helper method toInt64. This approach is consistent with other getter methods and promotes code reuse.


161-168: LGTM: VerifyNotBefore implementation is correct and consistent.

The VerifyNotBefore method correctly verifies the 'nbf' claim, handling the required flag appropriately. It follows the same pattern as other verification methods and reuses the verifyInt64Past function, which is good for consistency and code reuse.


261-320: LGTM: New types and functions for claim validation are well-designed.

The addition of ClaimValidationOption, ClaimValidationOptions, and the associated functions to create validation options is well-implemented. The use of the functional options pattern provides a flexible and extensible way to configure claim validation. Each option function is simple and focused, which is good for maintainability and readability.


321-392: LGTM: Helper functions are well-implemented and improve security.

The modifications and additions to the helper functions are well-implemented:

  • The use of subtle.ConstantTimeCompare in verifyAud is a good security practice to prevent timing attacks.
  • The new functions verifyAudAny and verifyAudAll provide more granular control over audience verification.
  • The additional helper functions verifyExp, verifyInt64Past, and verifyMapString are simple, focused, and enhance the overall functionality of the claims validation process.

These changes contribute to improved security and more flexible claim verification.

token/jwt/claims_jarm.go Outdated Show resolved Hide resolved
internal/test_helpers.go Outdated Show resolved Hide resolved
Copy link

@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: 5

🧹 Outside diff range and nitpick comments (10)
token/jwt/claims_test.go (2)

30-69: Well-structured test function with good coverage.

The TestFilter function is well-organized using table-driven tests, which is a good practice for testing multiple scenarios. The use of t.Run for each test case allows for clear reporting of individual test results.

However, consider the following suggestions to enhance the test coverage:

  1. Add a test case for when the filter contains keys not present in the map.
  2. Include a test case with non-string map keys to ensure type safety.

Example:

{
    "ShouldHandleNonExistentFilterKeys",
    map[string]any{"abc": 123},
    []string{"xyz"},
    map[string]any{"abc": 123},
},
{
    "ShouldHandleNonStringKeys",
    map[string]any{"abc": 123, 42: "answer"},
    []string{"abc"},
    map[string]any{42: "answer"},
},

Also, consider adding a brief comment explaining the purpose of the Filter function being tested, as it's not visible in this file.


44-48: Remove redundant test case.

The test case "ShouldFilterNoneNil" appears to be redundant with "ShouldFilterNone". Both cases test the scenario where an empty filter is applied. Consider removing one of these cases to avoid duplication.

token/jwt/claims_map.go (4)

77-124: LGTM: Comprehensive audience claim handling with a minor suggestion

The new methods for handling the 'aud' claim are well-implemented, providing flexibility for different audience verification scenarios. The code is DRY and handles edge cases appropriately.

Consider adding a comment explaining the purpose of the StringSliceFromMap function, as its implementation is not visible in this file.


177-243: LGTM: Comprehensive claim validation with a suggestion for improvement

The new Valid method provides a flexible and thorough way to validate all claims based on provided options. It correctly handles different validation scenarios and uses a ValidationError struct for detailed error reporting.

Consider refactoring this method to reduce its length and improve readability. You could extract the validation logic for each claim type into separate helper methods. For example:

func (m MapClaims) validateTimeClaims(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) {
    if !m.VerifyExpiresAt(now, vopts.expRequired) {
        vErr.Inner = errors.New("Token is expired")
        vErr.Errors |= ValidationErrorExpired
    }
    // ... (similar for iat and nbf)
}

func (m MapClaims) validateIssuer(vErr *ValidationError, vopts *ClaimValidationOptions) {
    if len(vopts.iss) != 0 && !m.VerifyIssuer(vopts.iss, true) {
        vErr.Inner = errors.New("Token has invalid issuer")
        vErr.Errors |= ValidationErrorIssuer
    }
}

// ... (similar methods for subject and audience)

func (m MapClaims) Valid(opts ...ClaimValidationOption) error {
    vopts := &ClaimValidationOptions{}
    for _, opt := range opts {
        opt(vopts)
    }

    now := m.getNow(vopts)
    vErr := new(ValidationError)

    m.validateTimeClaims(vErr, now, vopts)
    m.validateIssuer(vErr, vopts)
    m.validateSubject(vErr, vopts)
    m.validateAudience(vErr, vopts)

    if vErr.valid() {
        return nil
    }

    return vErr
}

This refactoring would make the Valid method more readable and easier to maintain.


260-268: LGTM: Useful helper method for numeric claims

The toInt64 method is a good addition, providing a convenient way to retrieve and convert numeric claims to int64. It correctly handles cases where the claim is not present.

Consider adding a brief comment explaining the purpose of the external toInt64 function, as its implementation is not visible in this file. This would improve code readability and maintainability.


331-377: LGTM: Comprehensive audience verification with a suggestion

The audience verification functions (verifyAud, verifyAudAny, verifyAudAll) are well-implemented and cover different verification scenarios. The use of constant-time comparison is good for security.

In the verifyAudAll function, the use of a labeled outer loop is correct but might be a bit hard to follow. Consider adding a brief comment explaining the logic, or refactor to use a helper function for clarity. For example:

func verifyAudAll(aud []string, cmp []string, required bool) bool {
    if len(aud) == 0 {
        return !required
    }

    return all(cmp, func(c string) bool {
        return any(aud, func(a string) bool {
            return subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1
        })
    })
}

func all(slice []string, predicate func(string) bool) bool {
    for _, item := range slice {
        if !predicate(item) {
            return false
        }
    }
    return true
}

func any(slice []string, predicate func(string) bool) bool {
    for _, item := range slice {
        if predicate(item) {
            return true
        }
    }
    return false
}

This refactoring might make the logic easier to understand at a glance.

token/jwt/claims_map_test.go (4)

987-993: Simplify the conditional check for error handling

The condition if len(tc.err) == 0 && tc.err == "" is redundant since tc.err == "" implies that len(tc.err) == 0. Additionally, the nested if tc.err != "" is unnecessary because this block is only executed when tc.err != "".

Apply this diff to simplify the condition and improve readability:

-if len(tc.err) == 0 && tc.err == "" {
+if tc.err == "" {
     assert.NoError(t, actual)
 } else {
-    if tc.err != "" {
         assert.EqualError(t, actual, tc.err)
-    }

924-930: Correct the validation option in the test case

In the test case "ShouldFailIssuer", the ValidateTimeFunc(time.Now) option seems unnecessary and may not impact the issuer validation. This could be confusing for future readers.

Remove the unnecessary validation option to focus on the issuer validation:

    			[]ClaimValidationOption{
    				ValidateIssuer("abc2"),
-					ValidateTimeFunc(time.Now),
    			},

983-1008: Simplify error variable declaration

The variable errs is used to accumulate error codes, but could be declared and used more concisely.

Apply this diff to simplify the error accumulation:

-	var errs uint32
 	for _, err := range tc.errs {
-		errs |= err
+		errs := err
 		assert.True(t, e.Has(err))
 	}
-	assert.Equal(t, errs, e.Errors)

473-545: Improve consistency in test case structure

In TestMapClaims_VerifySubject, the test cases use true and false for the required field, but there's no explanation of what required signifies. Adding comments or using a type with named fields can enhance readability.

Consider defining a type for the test cases or adding comments to clarify the purpose of each field.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 22bfc8c and dad6797.

📒 Files selected for processing (4)
  • token/jwt/claims_jwt.go (3 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/claims_map_test.go (1 hunks)
  • token/jwt/claims_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • token/jwt/claims_jwt.go
🔇 Additional comments (8)
token/jwt/claims_map.go (6)

27-75: LGTM: Well-implemented claim retrieval and verification methods

The new methods for getting and verifying 'iss' and 'sub' claims are well-implemented. They follow a consistent pattern, use constant-time comparison for security, and provide flexibility with optional requirement checks. This is a good addition to the MapClaims type.


126-174: LGTM: Improved time-based claim handling

The modifications to the time-based claim methods (exp, iat, nbf) are well-implemented. The use of helper functions (toInt64, verifyInt64Future, verifyInt64Past) improves code consistency and readability. The methods correctly handle cases where claims are not present.


247-257: LGTM: Improved JSON unmarshaling for claims

The modification to UnmarshalJSON is a good improvement. Setting the number type to UnmarshalIntOrFloat ensures that numeric claims are correctly parsed as either integers or floats, which is appropriate for JWT claims. The use of errorsx.WithStack(err) provides better error context for debugging.


270-329: LGTM: Well-designed claim validation configuration

The new ClaimValidationOption type, ClaimValidationOptions struct, and related functions provide a flexible and extensible way to configure claim validation. The use of the functional options pattern is a good choice, allowing for easy addition of new options in the future. Each option function is focused and easy to understand.

This design will make it easy for users of the library to customize claim validation according to their needs.


380-403: LGTM: Well-implemented helper functions for claim verification

The new helper functions (verifyInt64Future, verifyInt64Past, verifyMapString) are well-implemented and consistent with the rest of the code. They correctly handle cases where the value is not set and respect the required parameter. The use of constant-time comparison in verifyMapString is good for security.

These functions contribute to the overall robustness and security of the claim verification process.


Line range hint 1-403: Overall assessment: High-quality enhancements to JWT claim handling

The changes to token/jwt/claims_map.go significantly improve the JWT claim handling and verification capabilities of the system. The new methods and functions are well-implemented, following good practices such as constant-time comparisons for security-sensitive operations and providing flexibility through functional options for claim validation.

Key improvements include:

  1. Comprehensive methods for retrieving and verifying individual claims.
  2. A flexible Valid method for validating multiple claims with customizable options.
  3. Enhanced audience verification with support for different scenarios.
  4. Improved JSON unmarshaling for claims.

While the overall quality is high, consider the suggested minor improvements:

  1. Adding comments for clarity in some areas, especially for external functions.
  2. Potential refactoring of the Valid method to improve readability.
  3. Simplifying the verifyAudAll function for better clarity.

These changes provide a robust foundation for JWT handling in the system.

token/jwt/claims_map_test.go (2)

921-930: Ensure error messages match expected validation failures

In the test cases "ShouldFailIssuer" and "ShouldFailIssuerAbsent", the expected error message is "Token has invalid issuer", which suggests a specific validation failure. Confirm that this message aligns with the actual error produced by the Valid method.

Run the tests to ensure that the error messages match the expected output.


11-14: ⚠️ Potential issue

Verify the correctness of the import path

The import statement is referencing "authelia.com/provider/oauth2/internal/consts", which may not be intended in the context of this project under Ory Corp. This could introduce unintended dependencies or conflicts.

Run the following script to check for unintended imports from authelia.com:

Please confirm if the import path is correct and update it if necessary.

token/jwt/claims_map_test.go Outdated Show resolved Hide resolved
token/jwt/claims_map_test.go Show resolved Hide resolved
token/jwt/claims_map_test.go Show resolved Hide resolved
token/jwt/claims_map_test.go Outdated Show resolved Hide resolved
token/jwt/claims_map_test.go Outdated Show resolved Hide resolved
Copy link

@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: 6

🧹 Outside diff range and nitpick comments (8)
authorize_request_handler.go (4)

69-75: LGTM! Consider adding a comment for clarity.

The changes improve the handling of the signing algorithm, especially for cases where it's not specified. The introduction of algAny is a good approach.

Consider adding a brief comment explaining the purpose of algAny, e.g.:

// algAny is true when no specific algorithm is set, allowing any algorithm

121-138: Improved JWT decoding and validation. Consider adding debug logging.

The changes significantly enhance the JWT decoding and validation process. The use of a strategy pattern and the introduction of header validation options improve the code's flexibility and security.

Consider adding debug logging before and after the token decoding and validation steps. This could help with troubleshooting in production environments. For example:

f.Config.GetLogger(ctx).Debug("Decoding JWT token")
token, err := strategy.Decode(ctx, assertion, jwt.WithSigAlgorithm(jwt.SignatureAlgorithmsNone...), jwt.WithJARClient(client))
if err != nil {
    f.Config.GetLogger(ctx).WithError(err).Debug("Failed to decode JWT token")
    return errorsx.WithStack(fmtRequestObjectDecodeError(token, client, issuer, openid, err))
}
f.Config.GetLogger(ctx).Debug("Successfully decoded JWT token")

193-206: Robust issuer and claim validation. Consider extracting time function.

The addition of issuer validation and the enhanced claim validation logic significantly improve the security and correctness of the JWT processing. The use of custom validation options is a good approach.

Consider extracting the time function to a separate, testable function. This would improve testability and make it easier to mock time in unit tests. For example:

func getNowUTC() time.Time {
    return time.Now().UTC()
}

// Then in the validation options:
jwt.ValidateTimeFunc(getNowUTC),

522-596: Comprehensive error handling for JWT validation. Consider breaking down the function.

The fmtRequestObjectDecodeError function provides excellent, detailed error messages for various JWT validation scenarios. This will greatly improve debuggability and help in identifying specific issues with JWTs.

While the function is comprehensive, its length and complexity might make it harder to maintain. Consider breaking it down into smaller, more focused functions. For example:

func fmtRequestObjectDecodeError(token *jwt.Token, client JARClient, issuer string, openid bool, inner error) (outer *RFC6749Error) {
    outer = ErrInvalidRequestObject.WithWrap(inner).WithHintf("%s request object could not be decoded or validated.", hintRequestObjectPrefix(openid))

    if errJWTValidation := new(jwt.ValidationError); errors.As(inner, &errJWTValidation) {
        return handleJWTValidationError(errJWTValidation, token, client, issuer, openid)
    } else if errJWKLookup := new(jwt.JWKLookupError); errors.As(inner, &errJWKLookup) {
        return handleJWKLookupError(errJWKLookup, client, openid)
    } else {
        return handleGenericError(inner, client, openid)
    }
}

func handleJWTValidationError(errJWTValidation *jwt.ValidationError, token *jwt.Token, client JARClient, issuer string, openid bool) *RFC6749Error {
    // Handle different validation errors...
}

func handleJWKLookupError(errJWKLookup *jwt.JWKLookupError, client JARClient, openid bool) *RFC6749Error {
    // Handle JWK lookup errors...
}

func handleGenericError(err error, client JARClient, openid bool) *RFC6749Error {
    // Handle generic errors...
}

This approach would make the code more modular and easier to maintain.

authorize_request_handler_oidc_request_test.go (4)

31-31: Consider increasing RSA key size for more realistic testing.

The current RSA key size of 2048 bits is acceptable for testing purposes. However, for more realistic scenarios, consider using a 4096-bit key. This would better reflect current security recommendations for production environments.

Note: As per the retrieved learning, using a 1024-bit RSA key in test files is acceptable for specific scenarios or to keep tests running quickly. The current 2048-bit key is already an improvement over that.


Line range hint 167-491: Consider grouping test cases for improved readability.

The test cases are comprehensive and cover a wide range of scenarios, which is excellent. However, the large number of test cases in a single slice can make it challenging to understand and maintain. Consider the following suggestions:

  1. Group related test cases into separate slices or sub-functions based on the scenario they're testing (e.g., valid requests, invalid signatures, algorithm mismatches).
  2. Use a table-driven test helper function to reduce repetition in test case definitions.
  3. Add comments to separate and explain different groups of test cases.

These changes could improve the readability and maintainability of the test suite.


Line range hint 493-529: Consider enhancing error assertions and result validation.

The test execution is generally well-structured, using subtests for each case. However, consider the following improvements:

  1. Use require.Equal instead of assert.Equal for critical assertions to stop the test immediately if they fail.
  2. Add more specific assertions for the error types, not just the error messages.
  3. Consider using a helper function to compare the expected and actual form values, which could provide more detailed output on mismatches.
  4. Add assertions to verify that no unexpected form values are present.

These changes could make the tests more robust and easier to debug when failures occur.


Line range hint 531-573: Consider improving helper functions for better error handling and clarity.

The helper functions are useful for generating test data, but consider the following improvements:

  1. Replace mustGenerateX functions with versions that return errors, and handle these errors in the test cases. This allows for more granular error handling and avoids panics in tests.
  2. Rename mangleSig to something more descriptive, like corruptSignature.
  3. Add comments to explain the purpose and behavior of each helper function.
  4. In mangleSig, consider using a more deterministic way to corrupt the signature for consistent test results.

These changes would improve the maintainability and clarity of the test helpers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between dad6797 and 06a64dd.

📒 Files selected for processing (6)
  • authorize_request_handler.go (5 hunks)
  • authorize_request_handler_oidc_request_test.go (9 hunks)
  • token/jwt/jwt_strategy.go (1 hunks)
  • token/jwt/jwt_strategy_test.go (1 hunks)
  • token/jwt/token.go (3 hunks)
  • token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • token/jwt/jwt_strategy.go
  • token/jwt/jwt_strategy_test.go
  • token/jwt/util.go
🧰 Additional context used
📓 Learnings (2)
authorize_request_handler.go (1)
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#140
File: authorize_request_handler.go:121-125
Timestamp: 2024-09-27T07:36:36.699Z
Learning: In `authorize_request_handler.go`, only clients explicitly configured to allow the `'none'` algorithm can use it, and validation is performed appropriately (e.g., at line 140).
authorize_request_handler_oidc_request_test.go (1)
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#66
File: authorize_request_handler_oidc_request_test.go:108-179
Timestamp: 2024-03-23T23:32:47.956Z
Learning: In test files (with the _test.go suffix), using a 1024-bit RSA key is acceptable for testing specific scenarios, such as the acceptance of insecure keys, or to keep tests running quickly. This practice is not applicable to production code.
🔇 Additional comments (4)
authorize_request_handler.go (1)

140-144: Excellent security improvement for 'none' algorithm handling.

This new check prevents the use of the 'none' algorithm when it's not explicitly allowed, which is a crucial security improvement. It helps protect against potential vulnerabilities where an attacker might try to bypass signature verification.

authorize_request_handler_oidc_request_test.go (2)

Line range hint 1-27: LGTM: Imports and package declaration are appropriate.

The imports cover the necessary standard libraries and third-party packages required for testing OAuth2 and JWT functionality. The copyright notice is present, and the package declaration is correct.


Line range hint 1-573: Overall, a comprehensive test suite with room for improvements.

This test file provides extensive coverage for OpenID Connect request object handling, which is commendable. It tests various scenarios, including different signing algorithms, valid and invalid requests, and error conditions. However, there are several areas where the file could be improved:

  1. Organization: Group related test cases and consider breaking down the large test function into smaller, more focused tests.
  2. Error Handling: Improve error assertions and consider returning errors from helper functions instead of using must functions that panic.
  3. Readability: Add more comments explaining the purpose of different test groups and helper functions.
  4. Consistency: Ensure consistent naming conventions and consider increasing key sizes for more realistic testing scenarios.

Addressing these points would enhance the maintainability, readability, and robustness of the test suite.

token/jwt/token.go (1)

538-541: Verify Handling of Multiple Headers in newToken Function

The check if len(parsedToken.Headers) != 1 enforces that only one header is present, returning an error otherwise. Ensure that tokens with multiple headers are correctly handled or explicitly unsupported, and that this behavior aligns with JWT specifications and project requirements.

token/jwt/token.go Outdated Show resolved Hide resolved
token/jwt/token.go Outdated Show resolved Hide resolved
token/jwt/token.go Show resolved Hide resolved
token/jwt/token.go Outdated Show resolved Hide resolved
Copy link

@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: 19

🧹 Outside diff range and nitpick comments (29)
token/jwt/validation_error.go (2)

22-29: Approved reordering and addition of ValidationErrorSubject

The reordering of existing constants and the addition of ValidationErrorSubject maintain consistency with the error type structure. The new constant for subject claim validation is a valuable addition.

For consistency, consider adding a period at the end of each constant's comment, similar to the format used for the new constants above.


32-32: Improved comment clarity for ValidationError struct

The updated comment provides a clearer explanation of the ValidationError struct's purpose.

Consider a minor grammatical improvement:

- // The ValidationError is an error implementation from Parse if token is not valid.
+ // ValidationError is an error implementation returned by Parse when a token is not valid.

This removes the unnecessary article and slightly rephrases for better clarity.

handler/oauth2/strategy_jwt_profile_test.go (4)

58-85: LGTM! Consider adding a comment explaining the purpose of this function.

The new jwtInvalidTypCase function is well-structured and serves as a good test case for invalid JWT types. It correctly sets up a JWT with an invalid type in the header, which is crucial for testing error handling in the JWT validation process.

Consider adding a brief comment above the function to explain its purpose and how it differs from jwtValidCase. This would enhance code readability and make the test case's intention clearer to other developers.


Line range hint 150-168: LGTM! Consider using consistent time offsets for improved readability.

The addition of the now parameter to jwtExpiredCase is a great improvement. It allows for more deterministic testing of expiration logic by decoupling the test from the current system time.

For improved readability and consistency, consider using the same time unit for all offsets. For example:

IssuedAt:  now.UTC().Add(-time.Hour),
NotBefore: now.UTC().Add(-time.Hour),
ExpiresAt: now.UTC().Add(-time.Minute * 59),

This makes it easier to understand the relative timing of each field at a glance.


195-195: LGTM! Consider using a more readable timestamp format.

The updates to TestAccessToken improve the test's determinism and configuration. The use of constants for header type checking is a good practice for maintainability.

For improved readability, consider using time.Date() instead of time.Unix() for the specific timestamp. For example:

r: jwtExpiredCase(oauth2.AccessToken, time.Date(2024, 9, 22, 12, 0, 0, 0, time.UTC)),

This makes it easier for developers to understand the exact date and time being used in the test.

Also applies to: 214-219, 252-252


Line range hint 1-307: Great improvements to test determinism and coverage!

The changes in this file significantly enhance the robustness and maintainability of the JWT-related tests. The introduction of the now parameter for expired token tests, the new jwtInvalidTypCase function, and the use of constants instead of hardcoded values are all excellent improvements.

Consider adding a test case for a JWT with an expiration time very close to the current time (e.g., 1 second in the future). This edge case could help ensure that the system handles near-expiration scenarios correctly.

token/jwt/claims_map.go (2)

177-246: LGTM: Valid implementation is comprehensive and flexible.

The Valid function provides a thorough validation of all standard claims (exp, iat, nbf, iss, sub, aud) using the previously defined verification methods. This ensures consistency across the package. The use of ValidationError allows for detailed error reporting, which is excellent for debugging and error handling.

The function's flexibility through the use of options is a great design choice, allowing users to customize the validation process as needed.

Consider adding a comment explaining the purpose of the ValidationError struct and how it accumulates errors. This would improve code readability and maintainability.


270-329: LGTM: Claim validation options are well-designed and comprehensive.

The implementation of claim validation options using the functional options pattern is an excellent choice for API design. It provides flexibility and extensibility while keeping the code clean and easy to use. Each option function is focused on a single task, which adheres to the Single Responsibility Principle.

The options cover all standard claims (iss, sub, aud, exp, iat, nbf) and allow for customization of validation behavior, including the ability to set custom time functions and require specific claims.

Consider adding a brief comment for each validation option function explaining its purpose and any default behavior. This would improve the self-documentation of the code and make it easier for users to understand the available options without referring to external documentation.

client_authentication.go (8)

164-165: LGTM. Consider adding documentation.

The addition of GetAuthSigningKeyID method enhances the flexibility of client authentication. It would be beneficial to add a comment explaining the purpose and expected return value of this method.


169-174: LGTM. Consider adding documentation for new methods.

The addition of GetAuthEncryptionKeyID, GetAuthEncryptionAlg, and GetAuthEncryptionEnc methods suggests support for encrypted client authentication, which is a good security enhancement. It would be beneficial to add comments explaining the purpose and expected return values of these methods.


182-226: LGTM. Consider implementing encryption-related methods.

The EndpointClientAuthJWTClient struct is a good addition for handling JWT-based client authentication. However, some methods return empty strings or fixed values:

  1. GetSigningKeyID (line 204)
  2. GetEncryptionKeyID (line 212)
  3. GetEncryptionAlg (line 216)
  4. GetEncryptionEnc (line 220)

Consider implementing these methods to return meaningful values if JWT encryption is supported, or add comments explaining why they return empty strings if this is intentional.


233-236: Consider implementing GetAuthSigningKeyID or add a comment.

The GetAuthSigningKeyID method currently returns an empty string. If a signing key ID is required for token endpoint authentication, consider implementing this method. If returning an empty string is intentional, please add a comment explaining the rationale.


241-252: Consider implementing encryption-related methods or add comments.

The following methods currently return empty strings:

  1. GetAuthEncryptionKeyID (line 242)
  2. GetAuthEncryptionAlg (line 246)
  3. GetAuthEncryptionEnc (line 250)

If encryption is supported for token endpoint authentication, consider implementing these methods. If returning empty strings is intentional, please add comments explaining the rationale for each method.


267-286: Consider implementing authentication-related methods or add comments.

The following methods currently return empty strings:

  1. GetAuthSigningKeyID (line 268)
  2. GetAuthEncryptionKeyID (line 276)
  3. GetAuthEncryptionAlg (line 280)
  4. GetAuthEncryptionEnc (line 284)

If signing and encryption are supported for introspection endpoint authentication, consider implementing these methods. If returning empty strings is intentional, please add comments explaining the rationale for each method.


301-320: Consider implementing authentication-related methods or add comments.

The following methods currently return empty strings:

  1. GetAuthSigningKeyID (line 302)
  2. GetAuthEncryptionKeyID (line 310)
  3. GetAuthEncryptionAlg (line 314)
  4. GetAuthEncryptionEnc (line 318)

If signing and encryption are supported for revocation endpoint authentication, consider implementing these methods. If returning empty strings is intentional, please add comments explaining the rationale for each method.


Line range hint 1-330: Overall, good improvements to client authentication. Consider addressing the following:

  1. Add documentation to new methods in the EndpointClientAuthHandler interface (lines 164-174).
  2. Implement or explain the rationale for methods returning empty strings in EndpointClientAuthJWTClient (lines 182-226).
  3. For each endpoint handler (TokenEndpointClientAuthHandler, IntrospectionEndpointClientAuthHandler, RevocationEndpointClientAuthHandler), implement or explain the rationale for methods returning empty strings.

These changes significantly enhance the flexibility of client authentication. Ensuring proper implementation or documentation for the methods currently returning empty strings will improve the overall quality and maintainability of the code.

client_authentication_strategy.go (3)

Line range hint 29-67: LGTM! Improved flexibility in client authentication.

The changes to the AuthenticateClient method significantly enhance its capabilities, particularly in handling JWT assertions. The switch from resolver to handler provides a more generalized approach to endpoint authentication.

One minor suggestion:

Consider adding a comment explaining the purpose of the handler parameter, as it's a significant change from the previous resolver. This would help future developers understand the rationale behind this change.

-func (s *DefaultClientAuthenticationStrategy) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values, handler EndpointClientAuthHandler) (client Client, method string, err error) {
+// AuthenticateClient authenticates a client using various methods including JWT assertions.
+// The 'handler' parameter provides endpoint-specific authentication logic, replacing the previous 'resolver'.
+func (s *DefaultClientAuthenticationStrategy) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values, handler EndpointClientAuthHandler) (client Client, method string, err error) {

137-186: LGTM! Significant improvements to client assertion handling.

The NewClientAssertion function has been greatly enhanced with more robust JWT validation and error handling. The use of jwt.Strategy provides flexibility in JWT processing, which is excellent for supporting various client authentication scenarios.

One suggestion for improvement:

Consider adding more detailed error messages for different failure scenarios. This could help in troubleshooting client authentication issues. For example:

 if token, err = strategy.Decode(ctx, assertion, jwt.WithAllowUnverified(), jwt.WithSigAlgorithm(jwt.SignatureAlgorithmsNone...)); err != nil {
-    return &ClientAssertion{Assertion: assertion, Type: assertionType}, resolveJWTErrorToRFCError(err)
+    return &ClientAssertion{Assertion: assertion, Type: assertionType}, resolveJWTErrorToRFCError(err).WithHintf("Failed to decode client assertion of type '%s'.", assertionType)
 }

389-464: LGTM! Excellent addition for detailed JWT error reporting.

The fmtClientAssertionDecodeError function is a valuable addition that provides comprehensive and detailed error reporting for JWT validation issues during client authentication. This will greatly assist in debugging and troubleshooting JWT-related problems.

One minor suggestion for consistency:

Consider using consistent string formatting across all error messages. Some messages use %s for string interpolation while others use '%s'. Standardizing on one format (preferably '%s' for clearer distinction of interpolated values) would improve readability. For example:

-return outer.WithDebugf("OAuth 2.0 client with id '%s' provided a client assertion that was malformed. %s.", client.GetID(), strings.TrimPrefix(errJWTValidation.Error(), "go-jose/go-jose: "))
+return outer.WithDebugf("OAuth 2.0 client with id '%s' provided a client assertion that was malformed. '%s'.", client.GetID(), strings.TrimPrefix(errJWTValidation.Error(), "go-jose/go-jose: "))

Apply this change consistently across all error messages in the function for improved readability and maintainability.

authorize_request_handler_oidc_request_test.go (6)

32-41: Improved key generation for test cases

The changes introduce new key generation for RSA and ECDSA, which is good for testing different scenarios. However, consider the following improvements:

  1. Use a constant key size for RSA to ensure consistency across tests.
  2. Consider using a named curve constant for ECDSA key generation for better readability.

Example:

-keyRSA, err := rsa.GenerateKey(rand.Reader, 2048)
+const rsaKeySize = 2048
+keyRSA, err := rsa.GenerateKey(rand.Reader, rsaKeySize)

-keyECDSA, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+keyECDSA, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

56-110: Comprehensive JWK set definitions

The addition of multiple JWK definitions for different algorithms (RSA, ECDSA) and uses (signing, encryption) is excellent for thorough testing. However, there's room for improvement in code organization:

  1. Consider grouping related JWKs together (e.g., all RSA keys, all ECDSA keys).
  2. Use constants for repeated values like key IDs and algorithms to improve maintainability.

Example:

const (
    kidRS256Sig = "rs256-sig"
    kidES256Sig = "es256-sig"
    kidES256Enc = "es256-enc"
)

jwkPublicSigRSA := &jose.JSONWebKey{
    Key:       keyRSA.Public(),
    KeyID:     kidRS256Sig,
    Algorithm: string(jose.RS256),
    Use:       consts.JSONWebTokenUseSignature,
}

jwkPrivateSigRSA := &jose.JSONWebKey{
    Key:       keyRSA,
    KeyID:     kidRS256Sig,
    Algorithm: string(jose.RS256),
    Use:       consts.JSONWebTokenUseSignature,
}

// ... (group other related JWKs)

112-129: JWKS creation and debugging output

The creation of jwksPrivate and jwksPublic is good for organizing the keys. However, there's an unnecessary debug print statement:

fmt.Println(jwksPrivate)

This line should be removed as it's not needed in the test and may clutter the output.


Line range hint 150-179: HTTP test server setup

The setup of the HTTP test server with various endpoints for different test scenarios is well-organized. However, consider the following improvements:

  1. Use a const block for the endpoint paths to improve maintainability.
  2. Consider using a map to store the handlers, which can make it easier to add or modify endpoints in the future.

Example:

const (
    endpointJWKS = "/jwks.json"
    endpointValidStandard = "/request-object/valid/standard.jwk"
    // ... (other endpoint constants)
)

handlers := map[string]http.HandlerFunc{
    endpointJWKS: handlerJWKS,
    endpointValidStandard: handleString(assertionRequestObjectValid),
    // ... (other handlers)
}

for path, handler := range handlers {
    mux.Handle(path, handler)
}

Line range hint 181-566: Comprehensive test cases

The test cases cover a wide range of scenarios, which is excellent for ensuring the robustness of the implementation. However, the large number of test cases makes the code harder to read and maintain. Consider the following improvements:

  1. Group related test cases into subtests using t.Run().
  2. Use table-driven tests with more descriptive struct fields.
  3. Consider moving the test case definitions to a separate function or even a separate file to improve readability of the main test function.

Example:

func TestAuthorizeRequestParametersFromOpenIDConnectRequestObject(t *testing.T) {
    testCases := getTestCases(t)

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // ... (existing test logic)
        })
    }
}

func getTestCases(t *testing.T) []struct {
    name        string
    description string
    have        url.Values
    par         bool
    client      Client
    expected    url.Values
    wantErr     bool
    errType     error
    errString   string
    errRegex    *regexp.Regexp
} {
    return []struct{
        // ... (test case definitions)
    }{
        {
            name:        "ValidRequestObject",
            description: "Should pass with a valid request object",
            have:        url.Values{consts.FormParameterScope: {"foo openid"}, consts.FormParameterClientID: {"foo"}, consts.FormParameterResponseType: {consts.ResponseTypeImplicitFlowToken}, consts.FormParameterRequest: {assertionRequestObjectValid}},
            client:      &DefaultJARClient{JSONWebKeys: jwksPublic, RequestObjectSigningAlg: "RS256", DefaultClient: &DefaultClient{ID: "foo"}},
            expected:    url.Values{consts.FormParameterScope: {"foo openid"}, consts.FormParameterClientID: {"foo"}, consts.FormParameterResponseType: {consts.ResponseTypeImplicitFlowToken}, consts.FormParameterResponseMode: {consts.ResponseModeFormPost}, consts.FormParameterRequest: {assertionRequestObjectValid}, "foo": {"bar"}, "baz": {"baz"}},
        },
        // ... (other test cases)
    }
}

Line range hint 568-621: Test execution logic

The test execution logic is sound, but there are a few areas for improvement:

  1. Consider using require instead of assert for critical checks that should stop the test if they fail.
  2. The error checking logic could be simplified using a switch statement.
  3. The form parameter checking could be extracted into a helper function for better readability.

Example:

func TestAuthorizeRequestParametersFromOpenIDConnectRequestObject(t *testing.T) {
    // ... (existing setup code)

    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            // ... (existing setup code)

            err := provider.authorizeRequestParametersFromOpenIDConnectRequestObject(context.Background(), r, tc.par)

            switch {
            case tc.wantErr:
                require.Error(t, err)
                if tc.errType != nil {
                    assert.ErrorIs(t, err, tc.errType)
                }
                if tc.errString != "" {
                    assert.EqualError(t, ErrorToDebugRFC6749Error(err), tc.errString)
                }
                if tc.errRegex != nil {
                    assert.Regexp(t, tc.errRegex, ErrorToDebugRFC6749Error(err).Error())
                }
            default:
                require.NoError(t, err)
                assertFormParameters(t, tc.expected, r.Form)
            }
        })
    }
}

func assertFormParameters(t *testing.T, expected, actual url.Values) {
    assert.Equal(t, len(expected), len(actual))
    for k, v := range expected {
        assert.EqualValues(t, v, actual[k], fmt.Sprintf("Parameter %s did not match", k))
    }
}
token/jwt/util.go (3)

375-384: Clarify Error Message When Handling Encrypted Tokens

In the getJWTSignature function, when the token has five segments (indicating it's an encrypted JWT), the error message returned is "invalid token: the token is probably encrypted" (line 378~). Since encrypted tokens are valid JWTs, labeling them as "invalid" might be misleading.

Consider updating the error message to more accurately reflect the situation:

-return "", errors.WithStack(errors.New("invalid token: the token is probably encrypted"))
+return "", errors.WithStack(errors.New("cannot extract signature from an encrypted token"))

This clarifies that the inability to extract a signature is due to the token being encrypted, not because the token is invalid.


417-427: Caution Advisable When Using Unsafe JWT Parsing

The UnsafeParseSignedAny function parses JWTs without verifying signatures or validating claims (lines 422~). This could pose a security risk if used improperly, as it may allow malicious tokens to be processed without verification.

Ensure that this function is only used in controlled scenarios where the tokens are trusted, and the lack of verification does not introduce vulnerabilities. Consider adding explicit documentation or comments to highlight the security implications and guide developers on safe usage.


96-145: Validate Presence and Type of Essential JWE Header Fields

In the headerValidateJWE function, the code correctly checks for the presence and type of the kid, alg, and enc header fields. However, it might be beneficial to include additional validation for the cty (Content Type) header if it's required in your use case.

Consider verifying the cty header to ensure it matches expected values, which can help prevent token misinterpretation and enhance security.

token/jwt/client.go (1)

7-70: Add unit tests for client constructors and interfaces.

To ensure the reliability of the new client implementations, please add unit tests for the constructors (e.g., NewJARClient, NewIDTokenClient) and verify that the decorated clients correctly implement the Client interface.

Would you like me to help generate the unit test code or open a GitHub issue to track this task?

Also applies to: 72-82

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 06a64dd and 95370b6.

⛔ Files ignored due to path filters (2)
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (78)
  • access_error_test.go (2 hunks)
  • access_write_test.go (1 hunks)
  • authorize_request_handler.go (5 hunks)
  • authorize_request_handler_oidc_request_test.go (9 hunks)
  • client.go (6 hunks)
  • client_authentication.go (3 hunks)
  • client_authentication_jwks_strategy.go (3 hunks)
  • client_authentication_secret.go (1 hunks)
  • client_authentication_secret_plaintext.go (1 hunks)
  • client_authentication_strategy.go (8 hunks)
  • client_authentication_test.go (27 hunks)
  • compose/compose.go (1 hunks)
  • compose/compose_oauth2.go (1 hunks)
  • compose/compose_openid.go (3 hunks)
  • compose/compose_strategy.go (2 hunks)
  • config.go (3 hunks)
  • config_default.go (8 hunks)
  • fosite.go (2 hunks)
  • generate-mocks.sh (1 hunks)
  • generate.go (1 hunks)
  • handler/oauth2/introspector_jwt.go (1 hunks)
  • handler/oauth2/introspector_jwt_test.go (3 hunks)
  • handler/oauth2/strategy.go (1 hunks)
  • handler/oauth2/strategy_jwt_profile.go (4 hunks)
  • handler/oauth2/strategy_jwt_profile_test.go (6 hunks)
  • handler/oauth2/strategy_jwt_session.go (2 hunks)
  • handler/openid/flow_device_authorization_test.go (2 hunks)
  • handler/openid/flow_explicit_auth_test.go (1 hunks)
  • handler/openid/flow_explicit_token_test.go (1 hunks)
  • handler/openid/flow_hybrid.go (1 hunks)
  • handler/openid/flow_hybrid_test.go (4 hunks)
  • handler/openid/flow_implicit.go (1 hunks)
  • handler/openid/flow_implicit_test.go (2 hunks)
  • handler/openid/flow_refresh_token_test.go (2 hunks)
  • handler/openid/helper_test.go (1 hunks)
  • handler/openid/strategy_jwt.go (4 hunks)
  • handler/openid/strategy_jwt_test.go (1 hunks)
  • handler/openid/validator.go (2 hunks)
  • handler/openid/validator_test.go (2 hunks)
  • handler/rfc7523/handler.go (1 hunks)
  • handler/rfc8693/custom_jwt_type_handler.go (3 hunks)
  • handler/rfc8693/id_token_type_handler.go (2 hunks)
  • handler/rfc8693/token_exchange_test.go (4 hunks)
  • integration/helper_setup_test.go (1 hunks)
  • integration/introspect_token_test.go (2 hunks)
  • internal/consts/client_auth_method.go (1 hunks)
  • internal/consts/const.go (1 hunks)
  • internal/consts/jwt.go (1 hunks)
  • internal/consts/spec.go (1 hunks)
  • internal/mock/client_secret.go (1 hunks)
  • internal/randx/sequence.go (3 hunks)
  • internal/test_helpers.go (2 hunks)
  • introspection_request_handler_test.go (0 hunks)
  • introspection_response_writer.go (1 hunks)
  • response_handler.go (1 hunks)
  • testing/mock/client.go (1 hunks)
  • testing/mock/pkce_storage.go (2 hunks)
  • token/jarm/generate.go (1 hunks)
  • token/jarm/types.go (1 hunks)
  • token/jwt/claims_id_token.go (2 hunks)
  • token/jwt/claims_jarm.go (1 hunks)
  • token/jwt/claims_jwt.go (3 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/claims_map_test.go (1 hunks)
  • token/jwt/claims_test.go (1 hunks)
  • token/jwt/client.go (1 hunks)
  • token/jwt/client_test.go (1 hunks)
  • token/jwt/consts.go (1 hunks)
  • token/jwt/issuer.go (1 hunks)
  • token/jwt/jwt.go (0 hunks)
  • token/jwt/jwt_strategy.go (1 hunks)
  • token/jwt/jwt_strategy_opts.go (1 hunks)
  • token/jwt/jwt_strategy_test.go (1 hunks)
  • token/jwt/jwt_test.go (0 hunks)
  • token/jwt/token.go (3 hunks)
  • token/jwt/token_test.go (6 hunks)
  • token/jwt/util.go (1 hunks)
  • token/jwt/validation_error.go (1 hunks)
💤 Files not reviewed due to no reviewable changes (3)
  • introspection_request_handler_test.go
  • token/jwt/jwt.go
  • token/jwt/jwt_test.go
✅ Files skipped from review due to trivial changes (1)
  • internal/mock/client_secret.go
🚧 Files skipped from review as they are similar to previous changes (62)
  • access_error_test.go
  • access_write_test.go
  • client.go
  • client_authentication_jwks_strategy.go
  • client_authentication_secret.go
  • client_authentication_secret_plaintext.go
  • compose/compose.go
  • compose/compose_oauth2.go
  • compose/compose_openid.go
  • compose/compose_strategy.go
  • config.go
  • config_default.go
  • fosite.go
  • generate-mocks.sh
  • generate.go
  • handler/oauth2/introspector_jwt.go
  • handler/oauth2/introspector_jwt_test.go
  • handler/oauth2/strategy.go
  • handler/oauth2/strategy_jwt_session.go
  • handler/openid/flow_device_authorization_test.go
  • handler/openid/flow_explicit_auth_test.go
  • handler/openid/flow_explicit_token_test.go
  • handler/openid/flow_hybrid.go
  • handler/openid/flow_hybrid_test.go
  • handler/openid/flow_implicit.go
  • handler/openid/flow_implicit_test.go
  • handler/openid/flow_refresh_token_test.go
  • handler/openid/helper_test.go
  • handler/openid/strategy_jwt.go
  • handler/openid/strategy_jwt_test.go
  • handler/openid/validator.go
  • handler/openid/validator_test.go
  • handler/rfc7523/handler.go
  • handler/rfc8693/custom_jwt_type_handler.go
  • handler/rfc8693/id_token_type_handler.go
  • handler/rfc8693/token_exchange_test.go
  • integration/helper_setup_test.go
  • integration/introspect_token_test.go
  • internal/consts/client_auth_method.go
  • internal/consts/const.go
  • internal/consts/jwt.go
  • internal/consts/spec.go
  • internal/randx/sequence.go
  • internal/test_helpers.go
  • introspection_response_writer.go
  • response_handler.go
  • testing/mock/client.go
  • testing/mock/pkce_storage.go
  • token/jarm/generate.go
  • token/jarm/types.go
  • token/jwt/claims_jarm.go
  • token/jwt/claims_jwt.go
  • token/jwt/claims_map_test.go
  • token/jwt/claims_test.go
  • token/jwt/client_test.go
  • token/jwt/consts.go
  • token/jwt/issuer.go
  • token/jwt/jwt_strategy.go
  • token/jwt/jwt_strategy_opts.go
  • token/jwt/jwt_strategy_test.go
  • token/jwt/token.go
  • token/jwt/token_test.go
🧰 Additional context used
📓 Learnings (2)
authorize_request_handler.go (1)
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#140
File: authorize_request_handler.go:121-125
Timestamp: 2024-09-27T07:36:36.699Z
Learning: In `authorize_request_handler.go`, only clients explicitly configured to allow the `'none'` algorithm can use it, and validation is performed appropriately (e.g., at line 140).
authorize_request_handler_oidc_request_test.go (1)
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#66
File: authorize_request_handler_oidc_request_test.go:108-179
Timestamp: 2024-03-23T23:32:47.956Z
Learning: In test files (with the _test.go suffix), using a 1024-bit RSA key is acceptable for testing specific scenarios, such as the acceptance of insecure keys, or to keep tests running quickly. This practice is not applicable to production code.
🔇 Additional comments (34)
token/jwt/validation_error.go (1)

9-21: Excellent addition of granular error types!

The new constants for specific validation errors greatly enhance the error reporting capabilities. This granularity will be beneficial for debugging and more precise error handling. The use of bitwise flags with iota is maintained, allowing for efficient combination of multiple error types.

token/jwt/claims_map.go (17)

28-38: LGTM: GetIssuer implementation is correct and safe.

The function properly retrieves the 'iss' claim, handles the case where the claim is not present, and uses safe type assertion. Good job!


40-50: LGTM: VerifyIssuer implementation is secure and flexible.

The function correctly verifies the 'iss' claim, handles the case where the claim is not present, and respects the required parameter. The use of verifyMapString suggests a constant-time comparison, which is good for security.


52-63: LGTM: GetSubject implementation is correct and consistent.

The function properly retrieves the 'sub' claim, handles the case where the claim is not present, and uses safe type assertion. The implementation is consistent with GetIssuer, which is good for maintainability.


65-75: LGTM: VerifySubject implementation is secure and consistent.

The function correctly verifies the 'sub' claim, handles the case where the claim is not present, and respects the required parameter. The implementation is consistent with VerifyIssuer, which is good for maintainability. The use of verifyMapString ensures a secure comparison.


77-86: LGTM: GetAudience implementation is flexible and robust.

The function properly retrieves the 'aud' claim and handles the case where the claim is not present. The use of StringSliceFromMap suggests proper handling of different audience formats (string or []string), which is a good practice for flexibility.


90-98: LGTM: VerifyAudience implementation is correct and flexible.

The function properly verifies the 'aud' claim, handles the case where the claim is not present, and respects the required parameter. The use of verifyAud suggests proper handling of audience verification.


100-111: LGTM: VerifyAudienceAll implementation is comprehensive.

The function properly verifies all values in the 'aud' claim against provided values, handles the case where the claim is not present, and respects the required parameter. The use of verifyAudAll ensures proper handling of multiple audience values, which is a good practice for flexibility.


113-124: LGTM: VerifyAudienceAny implementation is flexible and correct.

The function properly verifies any value in the 'aud' claim against provided values, handles the case where the claim is not present, and respects the required parameter. The use of verifyAudAny ensures proper handling of multiple audience values, providing flexibility in audience verification.


126-129: LGTM: GetExpiresAt implementation is concise and correct.

The function properly retrieves the 'exp' claim using the toInt64 helper method, which ensures correct type conversion and error handling.


133-141: LGTM: VerifyExpiresAt implementation is correct and flexible.

The function properly verifies the 'exp' claim, handles the case where the claim is not present, and respects the required parameter. The use of verifyInt64Future ensures proper handling of expiration time verification.


143-146: LGTM: GetIssuedAt implementation is concise and correct.

The function properly retrieves the 'iat' claim using the toInt64 helper method, ensuring correct type conversion and error handling.


150-158: LGTM: VerifyIssuedAt implementation is correct and flexible.

The function properly verifies the 'iat' claim, handles the case where the claim is not present, and respects the required parameter. The use of verifyInt64Past ensures proper handling of issued at time verification.


160-163: LGTM: GetNotBefore implementation is concise and correct.

The function properly retrieves the 'nbf' claim using the toInt64 helper method, ensuring correct type conversion and error handling.


167-175: LGTM: VerifyNotBefore implementation is correct and flexible.

The function properly verifies the 'nbf' claim, handles the case where the claim is not present, and respects the required parameter. The use of verifyInt64Past ensures proper handling of not before time verification.


247-258: LGTM: UnmarshalJSON implementation is robust and flexible.

The function uses a custom JSON decoder to handle numbers as either int or float, which is a good practice for dealing with different number formats in JWT claims. The error handling is appropriate, wrapping errors with additional context for better debugging.


Line range hint 1-403: Overall, excellent implementation with robust JWT claim handling.

The changes to this file significantly enhance the JWT claim handling capabilities. The implementation is thorough, covering all standard claims with proper validation, error handling, and security considerations. The use of helper functions and the functional options pattern demonstrates good software design practices.

Key strengths:

  1. Comprehensive coverage of all standard JWT claims.
  2. Consistent error handling and validation across different claim types.
  3. Flexible API design using the functional options pattern.
  4. Security considerations, such as constant-time comparisons for sensitive data.

Minor suggestions for improvement:

  1. Add more inline documentation, especially for the validation option functions.
  2. Consider adding a comment explaining the ValidationError struct in the Valid function.

Great job on these improvements!


260-268: LGTM: toInt64 method is correctly implemented.

The method properly handles the case where the claim is not present and delegates the actual conversion to a separate toInt64 function. This separation of concerns is a good practice.

Please ensure that the toInt64 function (which is not visible in this file) correctly handles all possible number formats (int, float, string) that could be present in a JWT claim. You can verify this by checking its implementation or its unit tests.

client_authentication_strategy.go (2)

Line range hint 14-26: LGTM! Important updates to authentication strategy.

The addition of the JWT import and the updates to the DefaultClientAuthenticationStrategy struct are significant improvements. These changes introduce JWT-based authentication capabilities and JWKS fetching, which are crucial for modern OAuth2 implementations.


Line range hint 70-136: LGTM! Consistent updates to the authenticate method.

The changes to the authenticate method align well with the updates made to the AuthenticateClient method. The replacement of resolver with handler is consistent, and the core authentication logic remains intact. This approach maintains existing functionality while accommodating the new JWT-based authentication capabilities.

authorize_request_handler_oidc_request_test.go (1)

42-54: New JWK definitions enhance test coverage

The addition of various JWK (JSON Web Key) definitions, including jwkNone, clientSecretHS256, and jwkEncAES256, improves the test coverage by allowing for different signing and encryption scenarios. This is a positive change that will help ensure the robustness of the implementation.

handler/oauth2/strategy_jwt_profile.go (2)

174-174: Confirm the correctness of the 'Encode' method usage

In the GenerateJWT method, the Encode function is called on s.Strategy. Please verify that all the options passed to Encode, such as jwt.WithClaims, jwt.WithHeaders, and jwt.WithJWTProfileAccessTokenClient, are correctly set up and that they align with the expected parameters of the method.


60-62: Validate proper use of the updated 'Strategy' in token validation

In the ValidateAccessToken method, the validateJWT function is called with s.Strategy. Ensure that s.Strategy correctly implements the necessary interface expected by validateJWT and that all dependent code has been updated to accommodate this change from Signer to Strategy.

Run the following script to ensure that s.Strategy satisfies the required interfaces:

token/jwt/client.go (1)

172-174: Verify the consistency of IsClientSigned() return values.

The IsClientSigned() method returns true in decoratedJARClient but false in other decorated client structs. Please confirm if this is intentional and reflects the expected behavior.

Also applies to: 234-236, 296-298, 356-358, 416-418, 478-480

authorize_request_handler.go (10)

12-12: LGTM!

The import of time is necessary for time-related validations in the JWT claims.


Line range hint 69-82: LGTM!

The handling of the alg parameter and the algAny flag is logically correct. It ensures appropriate processing based on the client's configured request object signing algorithm.


119-119: LGTM!

Retrieving the issuer using GetIDTokenIssuer is properly implemented.


128-134: LGTM!

The JWT header validation options are correctly set using the client's configurations. This ensures that the token headers are validated against the expected values.


136-138: LGTM!

The token header validation is appropriately performed using token.Valid(optsValidHeader...), and errors are correctly handled.


140-145: LGTM!

The additional check ensures that clients not explicitly allowing the 'none' algorithm cannot use it, which enhances security by preventing unauthorized use of unsecured tokens.


193-195: LGTM!

The code correctly handles the scenario where the issuer is not configured, returning a server error with a meaningful message.


197-203: LGTM!

The claim validation options are appropriately set, including the custom time function and validations for issuer and audience. This ensures the token claims are valid according to the expected values.


205-206: LGTM!

The claims are validated using claims.Valid(optsValidClaims...), and errors are properly handled using fmtRequestObjectDecodeError.


521-598: Refactoring suggestion for fmtRequestObjectDecodeError remains applicable.

This function is extensive and contains repetitive patterns. Consider refactoring to enhance readability and maintainability by:

  • Eliminating repetitive code through helper functions.
  • Utilizing structured error types to handle different error scenarios more robustly.

token/jwt/claims_id_token.go Outdated Show resolved Hide resolved
token/jwt/claims_id_token.go Outdated Show resolved Hide resolved
client_authentication_strategy.go Outdated Show resolved Hide resolved
client_authentication_strategy.go Outdated Show resolved Hide resolved
token/jwt/client.go Outdated Show resolved Hide resolved
token/jwt/client.go Outdated Show resolved Hide resolved
client_authentication_test.go Show resolved Hide resolved
client_authentication_test.go Show resolved Hide resolved
client_authentication_test.go Show resolved Hide resolved
Copy link

@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: 7

🧹 Outside diff range and nitpick comments (9)
client_authentication.go (1)

Line range hint 1-339: Overall review: Significant enhancements to client authentication, but clarification and documentation needed.

The changes to client_authentication.go represent a substantial enhancement to the client authentication capabilities, potentially supporting more advanced scenarios like JWE. However, there are several areas that need attention:

  1. Documentation: Add comprehensive comments explaining the purpose and expected behavior of new methods, especially those with empty implementations.
  2. Consistency: The pattern of empty implementations across different endpoint handlers suggests a deliberate design choice. Consider extracting this common behavior into a shared implementation or explaining the rationale behind this approach.
  3. Placeholder Implementations: If any of the empty implementations are placeholders, add TODO comments to indicate future work.
  4. Testing: Ensure that unit tests are updated or added to cover the new functionality and edge cases.
  5. Usage Examples: Consider adding usage examples or updating existing documentation to demonstrate how these new authentication capabilities should be used.

Given the significant changes to the authentication logic, it would be beneficial to provide a high-level architectural overview of the new authentication flow. This could be in the form of a comment at the package level or in a separate documentation file, explaining how the different components (EndpointClientAuthHandler, EndpointClientAuthJWTClient, and the various endpoint-specific handlers) interact and what scenarios they support.

token/jwt/claims_map_test.go (3)

17-127: LGTM! Comprehensive test cases for VerifyAudience.

The test cases for TestMapClaims_VerifyAudience are well-structured and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The table-driven test approach is a good practice for testing multiple scenarios efficiently.

Consider adding a test case for an empty audience slice to ensure the method handles this edge case correctly:

{
    "ShouldFailEmptyAudience",
    MapClaims{
        consts.ClaimAudience: []string{},
    },
    "foo",
    true,
    false,
},

550-640: LGTM with a suggestion for improvement.

The test cases for TestMapClaims_VerifyExpiresAt are well-structured and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is maintained.

Consider adding a test case for a future expiration time to ensure the method correctly handles tokens that have not yet expired:

{
    "ShouldPassFutureExpiration",
    MapClaims{
        consts.ClaimExpirationTime: int64(9999999999), // Far future timestamp
    },
    int64(123),
    true,
    true,
},

This additional test case would improve the coverage by verifying that the method correctly handles tokens with future expiration times.


850-1011: LGTM! Comprehensive test cases for the Valid method.

The test cases for TestMapClaims_Valid are well-structured and cover a wide range of scenarios, including different validation options and error cases. The error checking logic is thorough, verifying both error messages and specific error types.

Consider adding a nil check before accessing the ValidationError to improve robustness:

 errors.As(actual, &e)

-require.NotNil(t, e)
+if assert.NotNil(t, e) {
     var errs uint32

     for _, err := range tc.errs {
         errs |= err

         assert.True(t, e.Has(err))
     }

     assert.Equal(t, errs, e.Errors)
+}

This change ensures that the subsequent error checks are only performed if the error is not nil, preventing potential panics in case of unexpected error types.

token/jwt/client.go (5)

1-6: Add package-level documentation.

Consider adding a package comment to describe the purpose and usage of the jwt package. This improves readability and helps other developers understand the functionality provided.


3-5: Remove unnecessary import if not used.

Review whether the import of "github.com/go-jose/go-jose/v4" is required in this file. If it's not used, it can be removed to clean up the code.


80-91: Clarify the purpose of the IsClientSigned method.

The IsClientSigned() method in the Client interface returns a boolean, but it's not immediately clear what "client signed" signifies. Providing additional documentation or renaming the method to better reflect its purpose could enhance code clarity.


163-185: Consistent receiver naming for methods.

In decoratedJARClient, the receiver is named r. For consistency and clarity, consider renaming it to c or client, matching the naming convention used elsewhere.


260-271: Correct indentation and formatting in comments.

Some comments have inconsistent indentation, making them harder to read. Ensure that all comments are properly indented and formatted for better readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 95370b6 and b43bc14.

📒 Files selected for processing (9)
  • client.go (8 hunks)
  • client_authentication.go (3 hunks)
  • handler/oauth2/strategy_jwt_profile.go (4 hunks)
  • introspection_response_writer.go (1 hunks)
  • token/jarm/generate.go (1 hunks)
  • token/jarm/types.go (1 hunks)
  • token/jwt/claims_map_test.go (1 hunks)
  • token/jwt/client.go (1 hunks)
  • token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • client.go
  • introspection_response_writer.go
  • token/jarm/generate.go
  • token/jarm/types.go
  • token/jwt/util.go
🔇 Additional comments (17)
client_authentication.go (6)

164-166: LGTM. Consider adding documentation for the new method.

The addition of GetAuthSigningKeyID method to the EndpointClientAuthHandler interface is a good enhancement for flexible client authentication.

Could you provide more context on how this method will be used in the authentication flow? Also, consider adding a comment to explain the purpose and expected return value of this method.


170-178: LGTM. Consider enhancing documentation for the new encryption-related methods.

The addition of GetAuthEncryptionKeyID, GetAuthEncryptionAlg, and GetAuthEncryptionEnc methods to the EndpointClientAuthHandler interface is a good enhancement for supporting encrypted client authentication.

To improve clarity and maintainability:

  1. Add comments explaining the purpose and expected return values for each new method.
  2. Consider providing a brief overview of how these methods fit into the overall client authentication flow, possibly in a comment block at the interface level.

305-324: Clarify the purpose of new method implementations in RevocationEndpointClientAuthHandler and explain the pattern across handlers.

The newly added methods in RevocationEndpointClientAuthHandler follow the same pattern as in IntrospectionEndpointClientAuthHandler:

  • GetAuthSigningKeyID, GetAuthEncryptionKeyID, GetAuthEncryptionAlg, and GetAuthEncryptionEnc return empty strings.
  • GetAuthSigningAlg delegates to the client's method.
  1. Please clarify if this consistent pattern across different endpoint handlers is intentional. If so, consider extracting this common behavior into a shared implementation to reduce code duplication.
  2. Add comments explaining the rationale behind the empty string returns and the delegation of GetAuthSigningAlg.
  3. If these are placeholder implementations, please add TODO comments indicating the need for future implementation.
  4. Consider adding a comment at the package level explaining the overall authentication strategy across different endpoints and why certain methods are implemented differently.

To verify the consistency of implementations across different handlers, you can run the following script:

#!/bin/bash
# Description: Compare implementations of new methods across different endpoint handlers

echo "Comparing implementations of new methods across different endpoint handlers:"
rg --type go "(TokenEndpointClientAuthHandler|IntrospectionEndpointClientAuthHandler|RevocationEndpointClientAuthHandler).*(GetAuthSigningKeyID|GetAuthSigningAlg|GetAuthEncryptionKeyID|GetAuthEncryptionAlg|GetAuthEncryptionEnc)"

245-256: Clarify the purpose of empty encryption-related method implementations.

The newly added methods GetAuthEncryptionKeyID, GetAuthEncryptionAlg, and GetAuthEncryptionEnc in TokenEndpointClientAuthHandler all return empty strings.

  1. Is this intentional? If so, please add comments explaining why these methods return empty strings for the token endpoint.
  2. If encryption is not supported or not applicable for the token endpoint, consider adding a comment at the struct level explaining this.
  3. If these are placeholder implementations, please add TODO comments indicating the need for future implementation.

To verify the usage of these methods, you can run the following script:

#!/bin/bash
# Description: Check for usage of encryption-related methods in TokenEndpointClientAuthHandler

echo "Searching for usage of encryption-related methods in TokenEndpointClientAuthHandler:"
rg --type go "TokenEndpointClientAuthHandler.*(GetAuthEncryptionKeyID|GetAuthEncryptionAlg|GetAuthEncryptionEnc)"

271-290: Clarify the purpose of new method implementations in IntrospectionEndpointClientAuthHandler.

The newly added methods in IntrospectionEndpointClientAuthHandler have varying implementations:

  • GetAuthSigningKeyID, GetAuthEncryptionKeyID, GetAuthEncryptionAlg, and GetAuthEncryptionEnc return empty strings.
  • GetAuthSigningAlg delegates to the client's method.
  1. Please clarify if the empty string returns are intentional. If so, add comments explaining the rationale.
  2. Explain why GetAuthSigningAlg has a different implementation compared to other methods.
  3. If these are placeholder implementations, please add TODO comments indicating the need for future implementation.
  4. Consider adding a comment at the struct level explaining the overall authentication strategy for the introspection endpoint.

To verify the usage and implementation of these methods, you can run the following script:

#!/bin/bash
# Description: Check for usage and implementation of new methods in IntrospectionEndpointClientAuthHandler

echo "Searching for usage and implementation of new methods in IntrospectionEndpointClientAuthHandler:"
rg --type go "IntrospectionEndpointClientAuthHandler.*(GetAuthSigningKeyID|GetAuthSigningAlg|GetAuthEncryptionKeyID|GetAuthEncryptionAlg|GetAuthEncryptionEnc)"

186-230: LGTM with suggestions. Consider refining the implementation of EndpointClientAuthJWTClient.

The addition of EndpointClientAuthJWTClient is a good step towards supporting JWT-based client authentication. However, there are a few points to consider:

  1. Some methods (GetSigningKeyID, GetEncryptionKeyID, GetEncryptionAlg, GetEncryptionEnc) return empty strings. Is this intentional or are these placeholder implementations?
  2. The IsClientSigned method always returns true. This might be an oversimplification. Consider implementing a more nuanced check.
  3. Add comments to explain the purpose of this struct and its methods, especially for methods with non-obvious implementations.

To verify the usage and implementation of this new struct, you can run the following script:

✅ Verification successful

Apologies for the error in the previous script. Here's the corrected script to continue verification:

[/run_scripts]


Apologies for the error in the previous script. Here's the corrected script to continue verification:

[/run_scripts]


Verification Successful: EndpointClientAuthJWTClient Implementation is Intentional

The implementation of EndpointClientAuthJWTClient aligns with existing patterns in the codebase. The methods that return empty strings (GetSigningKeyID, GetEncryptionKeyID, GetEncryptionAlg, GetEncryptionEnc) are consistently implemented across various client handlers, indicating intentional design choices. Additionally, the IsClientSigned method is uniformly used, supporting its current implementation.

Recommendations:

  • Add Comments: Include explanatory comments within EndpointClientAuthJWTClient to clarify the purpose of methods returning empty strings and the rationale behind always returning true for IsClientSigned.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usage of EndpointClientAuthJWTClient and its methods

# Search for usage of EndpointClientAuthJWTClient
echo "Searching for usage of EndpointClientAuthJWTClient:"
rg --type go "EndpointClientAuthJWTClient"

# Search for implementations of methods that return empty strings
echo "\nSearching for implementations of methods that return empty strings:"
rg --type go "GetSigningKeyID|GetEncryptionKeyID|GetEncryptionAlg|GetEncryptionEnc"

# Search for usage of IsClientSigned
echo "\nSearching for usage of IsClientSigned:"
rg --type go "IsClientSigned"

Length of output: 2512


Script:

#!/bin/bash
# Description: Check for usage of EndpointClientAuthJWTClient and its methods

# Search for usage of EndpointClientAuthJWTClient
echo "Searching for usage of EndpointClientAuthJWTClient:"
rg --type go "EndpointClientAuthJWTClient"

# Search for implementations of methods that return empty strings
echo "Searching for implementations of methods that return empty strings:"
rg --type go "GetSigningKeyID|GetEncryptionKeyID|GetEncryptionAlg|GetEncryptionEnc"

# Search for usage of IsClientSigned
echo "Searching for usage of IsClientSigned:"
rg --type go "IsClientSigned"

Length of output: 6262


Script:

#!/bin/bash
# Description: Check for usage of EndpointClientAuthJWTClient and its methods

# Search for usage of EndpointClientAuthJWTClient
echo "Searching for usage of EndpointClientAuthJWTClient:"
rg --type go "EndpointClientAuthJWTClient"

# Search for implementations of methods that return empty strings
echo "Searching for implementations of methods that return empty strings:"
rg --type go "GetSigningKeyID|GetEncryptionKeyID|GetEncryptionAlg|GetEncryptionEnc"

# Search for usage of IsClientSigned
echo "Searching for usage of IsClientSigned:"
rg --type go "IsClientSigned"

Length of output: 6262

token/jwt/claims_map_test.go (6)

129-256: LGTM! Thorough test cases for VerifyAudienceAll.

The test cases for TestMapClaims_VerifyAudienceAll are well-designed and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is commendable.


389-470: LGTM! Comprehensive test cases for VerifyIssuer.

The test cases for TestMapClaims_VerifyIssuer are well-designed and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is maintained, which is excellent for readability and maintainability.


474-546: LGTM! Well-structured test cases for VerifySubject.

The test cases for TestMapClaims_VerifySubject are thorough and cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The consistency in using the table-driven test approach is maintained, which is excellent for readability and maintainability.


644-743: LGTM! Excellent test coverage for VerifyIssuedAt.

The test cases for TestMapClaims_VerifyIssuedAt are comprehensive and well-structured. They cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The inclusion of tests for both past and future issued at times is particularly noteworthy and ensures robust validation of the VerifyIssuedAt method.


747-846: LGTM! Thorough test coverage for VerifyNotBefore.

The test cases for TestMapClaims_VerifyNotBefore are well-designed and comprehensive. They cover a wide range of scenarios, including valid and invalid inputs, different claim types, and required/optional claims. The inclusion of tests for both past and future "not before" times ensures robust validation of the VerifyNotBefore method.


Line range hint 1-1011: Excellent test coverage and structure for MapClaims.

The token/jwt/claims_map_test.go file demonstrates high-quality test coverage for the MapClaims structure. Key strengths include:

  1. Consistent use of table-driven tests across all functions.
  2. Comprehensive coverage of various scenarios, including edge cases and error conditions.
  3. Clear and descriptive test case names.
  4. Thorough error checking, including verification of specific error types and bitwise error flags.

The minor suggestions provided in the review comments will further enhance the robustness and completeness of the test suite. Overall, this test file serves as an excellent example of thorough and well-structured unit testing.

handler/oauth2/strategy_jwt_profile.go (2)

177-187: Handle potential nil pointer dereference in validateJWT

The validateJWT function may cause a nil pointer dereference if strategy.Decode returns an error and a nil token. When token is nil, passing it to fmtValidateJWTError can lead to a panic when the function tries to access fields of token.


189-273: Refactor fmtValidateJWTError for improved maintainability

The fmtValidateJWTError function is lengthy and contains deeply nested conditional logic, which can make it difficult to read and maintain. Consider refactoring the function to enhance readability and maintainability. This could involve extracting repetitive code into helper functions or mapping error cases to corresponding error messages.

token/jwt/client.go (3)

159-185: Avoid code duplication in decorated client structs.

The decorated*Client structs and their methods largely duplicate code by delegating to underlying client interfaces. Consider refactoring to reduce repetition, possibly by using embedded types or a generic decorator pattern for shared behaviors.


221-248: Verify the IsClientSigned return value in decoratedIDTokenClient.

The IsClientSigned() method returns false in decoratedIDTokenClient, while it returns true in decoratedJARClient. Ensure that this behavior is intentional and aligns with the desired functionality.


471-475: Fix potential typos in documentation comments.

Double-check the comments for GetIntrospectionSignedResponseAlg() to ensure accuracy and correctness, as there may be residual typos or formatting errors.

client_authentication.go Show resolved Hide resolved
token/jwt/claims_map_test.go Outdated Show resolved Hide resolved
handler/oauth2/strategy_jwt_profile.go Outdated Show resolved Hide resolved
token/jwt/client.go Show resolved Hide resolved
Copy link

@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: 13

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between b43bc14 and 5df0ecf.

📒 Files selected for processing (3)
  • handler/oauth2/strategy_jwt_profile.go (4 hunks)
  • token/jwt/claims_map_test.go (1 hunks)
  • token/jwt/client.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • handler/oauth2/strategy_jwt_profile.go
🔇 Additional comments (5)
token/jwt/claims_map_test.go (5)

129-256: LGTM! Comprehensive test cases for VerifyAudienceAll.

The test cases for TestMapClaims_VerifyAudienceAll are well-structured and cover a wide range of scenarios, including edge cases. Good job on maintaining consistency with the previous test function.


389-470: LGTM! Comprehensive test cases for VerifyIssuer.

The test cases for TestMapClaims_VerifyIssuer are well-structured and cover a wide range of scenarios, including edge cases such as empty strings, nil values, and wrong types. Good job on maintaining consistency with the previous test functions.


644-743: LGTM! Comprehensive test cases for VerifyIssuedAt.

The test cases for TestMapClaims_VerifyIssuedAt are well-structured and cover a wide range of scenarios, including edge cases such as future and past issued times, different integer types, and nil values. The time-based comparisons are appropriately tested. Good job on maintaining consistency with the previous test functions and addressing time-related edge cases.


747-846: LGTM! Comprehensive test cases for VerifyNotBefore.

The test cases for TestMapClaims_VerifyNotBefore are well-structured and cover a wide range of scenarios, including edge cases such as future and past "not before" times, different integer types, and nil values. The time-based comparisons are appropriately tested. Good job on maintaining consistency with the previous test functions and addressing time-related edge cases.


Line range hint 1-1011: Overall, excellent test coverage with minor suggestions for improvement.

The claims_map_test.go file provides a comprehensive set of test cases for JWT claim validations. The tests are well-structured, consistent, and cover a wide range of scenarios including edge cases.

A few minor improvements have been suggested:

  1. Adding test cases for multiple audience values in TestMapClaims_VerifyAudience.
  2. Improving the "ShouldPassMultipleAny" test case in TestMapClaims_VerifyAudienceAny.
  3. Adding a test case for empty subject in TestMapClaims_VerifySubject.
  4. Adding test cases for time-based comparisons in TestMapClaims_VerifyExpiresAt.
  5. Improving error checking in TestMapClaims_Valid.
  6. Adding a test case for VerifyAudienceAny validation in TestMapClaims_Valid.

Implementing these suggestions will further enhance the robustness and coverage of the test suite. Great job on maintaining consistency and thoroughness throughout the test file!

Comment on lines 17 to 127
consts.ClaimAudience: []string{"foo"},
},
"foo",
true,
true,
},
{
"ShouldPassMultiple",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar"},
},
"foo",
true,
true,
},
{
"ShouldFailNoClaim",
MapClaims{},
"foo",
true,
false,
},
{
"ShouldFailNoMatch",
MapClaims{
consts.ClaimAudience: []string{"bar"},
},
"foo",
true,
false,
},
{
"ShouldPassNoClaim",
MapClaims{},
"foo",
false,
true,
},
{
"ShouldPassTypeAny",
MapClaims{
consts.ClaimAudience: []any{"foo"},
},
"foo",
true,
true,
},
{
"ShouldPassTypeString",
MapClaims{
consts.ClaimAudience: "foo",
},
"foo",
true,
true,
},
{
"ShouldFailTypeString",
MapClaims{
consts.ClaimAudience: "bar",
},
"foo",
true,
false,
},
{
"ShouldFailTypeNil",
MapClaims{
consts.ClaimAudience: nil,
},
"foo",
true,
false,
},
{
"ShouldFailTypeSliceAnyInt",
MapClaims{
consts.ClaimAudience: []any{1, 2, 3},
},
"foo",
true,
false,
},
{
"ShouldFailTypeInt",
MapClaims{
consts.ClaimAudience: 1,
},
"foo",
true,
false,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.VerifyAudience(tc.cmp, tc.required))
})
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding a test case for multiple audience values.

The test cases for TestMapClaims_VerifyAudience are comprehensive. However, to improve coverage, consider adding a test case where the audience claim contains multiple values, and the verification should pass if any of them match. This would test the behavior when the audience claim is a slice of strings.

Add a new test case:

{
    "ShouldPassMultipleAudiences",
    MapClaims{
        consts.ClaimAudience: []string{"foo", "bar", "baz"},
    },
    "bar",
    true,
    true,
},

Comment on lines 474 to 528
func TestMapClaims_VerifySubject(t *testing.T) {
testCases := []struct {
name string
have MapClaims
cmp string
required bool
expected bool
}{
{
"ShouldPass",
MapClaims{
consts.ClaimSubject: "foo",
},
"foo",
true,
true,
},
{
"ShouldFailNoClaim",
MapClaims{},
"foo",
true,
false,
},
{
"ShouldPassNoClaim",
MapClaims{},
"foo",
false,
true,
},
{
"ShouldFailNoMatch",
MapClaims{
consts.ClaimSubject: "bar",
},
"foo",
true,
false,
},
{
"ShouldFailWrongType",
MapClaims{
consts.ClaimSubject: 5,
},
"5",
true,
false,
},
{
"ShouldFailNil",
MapClaims{
consts.ClaimSubject: nil,
},
"foo",
true,
false,
},
{
"ShouldPassNil",
MapClaims{
consts.ClaimSubject: nil,
},
"foo",
false,
true,
},
}
want := true
got := mapClaims.VerifyAudience("foo", true)

if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.VerifySubject(tc.cmp, tc.required))
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding a test case for empty subject.

The test cases for TestMapClaims_VerifySubject are comprehensive and well-structured. However, to improve coverage, consider adding a test case where the subject claim is an empty string. This would test the behavior when the subject is present but empty.

Add a new test case:

{
    "ShouldFailEmptyString",
    MapClaims{
        consts.ClaimSubject: "",
    },
    "foo",
    true,
    false,
},

This test case would ensure that an empty string is not considered a valid subject when a specific subject is required.

Comment on lines 550 to 613
func TestMapClaims_VerifyExpiresAt(t *testing.T) {
testCases := []struct {
name string
have MapClaims
cmp int64
required bool
expected bool
}{
{
"ShouldPass",
MapClaims{
consts.ClaimExpirationTime: int64(123),
},
int64(123),
true,
true,
},
{
"ShouldPassStandardInt",
MapClaims{
consts.ClaimExpirationTime: 123,
},
int64(123),
true,
true,
},
{
"ShouldPassStandardInt32",
MapClaims{
consts.ClaimExpirationTime: int32(123),
},
int64(123),
true,
true,
},
{
"ShouldFailNoClaim",
MapClaims{},
int64(123),
true,
false,
},
{
"ShouldPassNoClaim",
MapClaims{},
int64(123),
false,
true,
},
{
"ShouldFailNoMatch",
MapClaims{
consts.ClaimExpirationTime: 4,
},
int64(123),
true,
false,
},
{
"ShouldFailWrongType",
MapClaims{
consts.ClaimExpirationTime: true,
},
int64(123),
true,
false,
},
{
"ShouldFailNil",
MapClaims{
consts.ClaimExpirationTime: nil,
},
int64(123),
true,
false,
},
{
"ShouldPassNil",
MapClaims{
consts.ClaimExpirationTime: nil,
},
int64(123),
false,
true,
},
}
want := false
got := mapClaims.VerifyAudience("foo", true)

if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.VerifyExpiresAt(tc.cmp, tc.required))
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding test cases for time-based comparisons.

The test cases for TestMapClaims_VerifyExpiresAt are well-structured and cover various scenarios. However, to improve coverage for time-based comparisons, consider adding the following test cases:

Add these new test cases:

{
    "ShouldPassFuture",
    MapClaims{
        consts.ClaimExpirationTime: int64(200),
    },
    int64(100),
    true,
    true,
},
{
    "ShouldFailPast",
    MapClaims{
        consts.ClaimExpirationTime: int64(100),
    },
    int64(200),
    true,
    false,
},

These additional test cases would ensure that the VerifyExpiresAt method correctly handles future and past expiration times relative to the comparison time.

Comment on lines +850 to +950
"ShouldFailIssuedFuture",
MapClaims{
consts.ClaimIssuedAt: 999999999999999,
},
nil,
[]uint32{ValidationErrorIssuedAt},
"Token used before issued",
},
{
"ShouldFailMultiple",
MapClaims{
consts.ClaimExpirationTime: 1,
consts.ClaimIssuedAt: 999999999999999,
},
nil,
[]uint32{ValidationErrorIssuedAt, ValidationErrorExpired},
"Token used before issued",
},
{
"ShouldPassIssuer",
MapClaims{
consts.ClaimIssuer: "abc",
},
[]ClaimValidationOption{ValidateIssuer("abc")},
nil,
"",
},
{
"ShouldFailIssuer",
MapClaims{
consts.ClaimIssuer: "abc",
},
[]ClaimValidationOption{ValidateIssuer("abc2"), ValidateTimeFunc(time.Now)},
[]uint32{ValidationErrorIssuer},
"Token has invalid issuer",
},
{
"ShouldFailIssuerAbsent",
MapClaims{},
[]ClaimValidationOption{ValidateIssuer("abc2")},
[]uint32{ValidationErrorIssuer},
"Token has invalid issuer",
},
{
"ShouldPassSubject",
MapClaims{
consts.ClaimSubject: "abc",
},
[]ClaimValidationOption{ValidateSubject("abc")},
nil,
"",
},
{
"ShouldFailSubject",
MapClaims{
consts.ClaimSubject: "abc",
},
[]ClaimValidationOption{ValidateSubject("abc2")},
[]uint32{ValidationErrorSubject},
"Token has invalid subject",
},
{
"ShouldFailSubjectAbsent",
MapClaims{},
[]ClaimValidationOption{ValidateSubject("abc2")},
[]uint32{ValidationErrorSubject},
"Token has invalid subject",
},
{
"ShouldPassAudienceAll",
MapClaims{
consts.ClaimAudience: []any{"abc", "123"},
},
[]ClaimValidationOption{ValidateAudienceAll("abc", "123")},
nil,
"",
},
{
"ShouldFailAudienceAll",
MapClaims{
consts.ClaimAudience: []any{"abc", "123"},
},
[]ClaimValidationOption{ValidateAudienceAll("abc", "123", "456")},
[]uint32{ValidationErrorAudience},
"Token has invalid audience",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := tc.have.Valid(tc.opts...)

if len(tc.err) == 0 && tc.err == "" {
assert.NoError(t, actual)
} else {
if tc.err != "" {
assert.EqualError(t, actual, tc.err)
}

var e *ValidationError
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error checking in TestMapClaims_Valid.

The error checking in the TestMapClaims_Valid function can be improved. Currently, the code doesn't check if the error type assertion was successful before accessing the ValidationError fields.

Update the error checking code as follows:

var e *ValidationError
if errors.As(actual, &e) {
    if tc.err != "" {
        assert.EqualError(t, actual, tc.err)
    }

    var errs uint32
    for _, err := range tc.errs {
        errs |= err
        assert.True(t, e.Has(err))
    }
    assert.Equal(t, errs, e.Errors)
} else {
    t.Errorf("Expected ValidationError, but got a different error type")
}

This change ensures that the code only accesses the ValidationError fields if the type assertion was successful, preventing potential nil pointer dereferences.

Comment on lines +850 to +966
{
"ShouldPassIssuer",
MapClaims{
consts.ClaimIssuer: "abc",
},
[]ClaimValidationOption{ValidateIssuer("abc")},
nil,
"",
},
{
"ShouldFailIssuer",
MapClaims{
consts.ClaimIssuer: "abc",
},
[]ClaimValidationOption{ValidateIssuer("abc2"), ValidateTimeFunc(time.Now)},
[]uint32{ValidationErrorIssuer},
"Token has invalid issuer",
},
{
"ShouldFailIssuerAbsent",
MapClaims{},
[]ClaimValidationOption{ValidateIssuer("abc2")},
[]uint32{ValidationErrorIssuer},
"Token has invalid issuer",
},
{
"ShouldPassSubject",
MapClaims{
consts.ClaimSubject: "abc",
},
[]ClaimValidationOption{ValidateSubject("abc")},
nil,
"",
},
{
"ShouldFailSubject",
MapClaims{
consts.ClaimSubject: "abc",
},
[]ClaimValidationOption{ValidateSubject("abc2")},
[]uint32{ValidationErrorSubject},
"Token has invalid subject",
},
{
"ShouldFailSubjectAbsent",
MapClaims{},
[]ClaimValidationOption{ValidateSubject("abc2")},
[]uint32{ValidationErrorSubject},
"Token has invalid subject",
},
{
"ShouldPassAudienceAll",
MapClaims{
consts.ClaimAudience: []any{"abc", "123"},
},
[]ClaimValidationOption{ValidateAudienceAll("abc", "123")},
nil,
"",
},
{
"ShouldFailAudienceAll",
MapClaims{
consts.ClaimAudience: []any{"abc", "123"},
},
[]ClaimValidationOption{ValidateAudienceAll("abc", "123", "456")},
[]uint32{ValidationErrorAudience},
"Token has invalid audience",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := tc.have.Valid(tc.opts...)

if len(tc.err) == 0 && tc.err == "" {
assert.NoError(t, actual)
} else {
if tc.err != "" {
assert.EqualError(t, actual, tc.err)
}

var e *ValidationError

errors.As(actual, &e)

require.NotNil(t, e)

var errs uint32

for _, err := range tc.errs {
errs |= err

assert.True(t, e.Has(err))
}

if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
assert.Equal(t, errs, e.Errors)
}
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add a test case for VerifyAudienceAny validation.

The TestMapClaims_Valid function covers various validation scenarios, but it's missing a test case for the VerifyAudienceAny validation. Adding this case would improve the overall test coverage.

Add the following test case to the testCases slice:

{
    "ShouldPassAudienceAny",
    MapClaims{
        consts.ClaimAudience: []any{"abc", "123", "xyz"},
    },
    []ClaimValidationOption{ValidateAudienceAny("123", "456")},
    nil,
    "",
},

This test case verifies that the Valid method correctly handles the VerifyAudienceAny validation when at least one of the provided audiences matches.

Comment on lines +430 to +432
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) {
return false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Modify IsClientSigned to reflect GetEnableJWTProfileOAuthAccessTokens in decoratedJWTProfileAccessTokenClient

The IsClientSigned method in decoratedJWTProfileAccessTokenClient always returns false. Considering that JWT Profile Access Tokens may be signed based on the client's configuration (GetEnableJWTProfileOAuthAccessTokens), it's advisable to have IsClientSigned reflect this.

Apply this diff to update the IsClientSigned method:

func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) {
-	return false
+	return r.GetEnableJWTProfileOAuthAccessTokens()
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) {
return false
}
func (r *decoratedJWTProfileAccessTokenClient) IsClientSigned() (is bool) {
return r.GetEnableJWTProfileOAuthAccessTokens()
}

Comment on lines +98 to +108
// GetClientSecretPlainText returns the ClientSecret as plaintext if available. The semantics of this function
// return values are important.
// If the client is not configured with a secret the return should be:
// - secret with value nil, ok with value false, and err with value of nil
// If the client is configured with a secret but is hashed or otherwise not a plaintext value:
// - secret with value nil, ok with value true, and err with value of nil
// If an error occurs retrieving the secret other than this:
// - secret with value nil, ok with value true, and err with value of the error
// If the plaintext secret is successful:
// - secret with value of the bytes of the plaintext secret, ok with value true, and err with value of nil
GetClientSecretPlainText() (secret []byte, ok bool, err error)
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify the return semantics of GetClientSecretPlainText

The method GetClientSecretPlainText has complex return semantics involving secret, ok, and err. This can lead to confusion and misuse. Consider simplifying the method to return only (secret []byte, err error), using a nil secret to indicate absence, and an error when appropriate. This aligns with Go conventions and improves clarity.

Apply this diff to simplify the method signature and adjust the documentation:

 // GetClientSecretPlainText returns the ClientSecret as plaintext if available.
-// The semantics of this function return values are important.
-// If the client is not configured with a secret the return should be:
-//   - secret with value nil, ok with value false, and err with value of nil
-// If the client is configured with a secret but is hashed or otherwise not a plaintext value:
-//   - secret with value nil, ok with value true, and err with value of nil
-// If an error occurs retrieving the secret other than this:
-//   - secret with value nil, ok with value true, and err with value of the error
-// If the plaintext secret is successful:
-//   - secret with value of the bytes of the plaintext secret, ok with value true, and err with value of nil
-GetClientSecretPlainText() (secret []byte, ok bool, err error)
+// Returns nil and an error if the secret cannot be retrieved.
+GetClientSecretPlainText() (secret []byte, err error)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// GetClientSecretPlainText returns the ClientSecret as plaintext if available. The semantics of this function
// return values are important.
// If the client is not configured with a secret the return should be:
// - secret with value nil, ok with value false, and err with value of nil
// If the client is configured with a secret but is hashed or otherwise not a plaintext value:
// - secret with value nil, ok with value true, and err with value of nil
// If an error occurs retrieving the secret other than this:
// - secret with value nil, ok with value true, and err with value of the error
// If the plaintext secret is successful:
// - secret with value of the bytes of the plaintext secret, ok with value true, and err with value of nil
GetClientSecretPlainText() (secret []byte, ok bool, err error)
// GetClientSecretPlainText returns the ClientSecret as plaintext if available.
// Returns nil and an error if the secret cannot be retrieved.
GetClientSecretPlainText() (secret []byte, err error)

Comment on lines +307 to +309
func (r *decoratedJARMClient) IsClientSigned() (is bool) {
return false
}
Copy link

@coderabbitai coderabbitai bot Sep 28, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider setting IsClientSigned to true for decoratedJARMClient

The IsClientSigned method in decoratedJARMClient returns false. However, per the JWT Secured Authorization Response Mode (JARM) specifications, authorization responses are expected to be signed, with RS256 as the default signing algorithm if not specified. Modifying IsClientSigned to return true will reflect the standard behavior.

Apply this diff to adjust the IsClientSigned method:

func (r *decoratedJARMClient) IsClientSigned() (is bool) {
-	return false
+	return true
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *decoratedJARMClient) IsClientSigned() (is bool) {
return false
}
func (r *decoratedJARMClient) IsClientSigned() (is bool) {
return true
}

Copy link
Member Author

Choose a reason for hiding this comment

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

IsClientSigned returns true if the signature is generated by the client, in the instance of JARM the signature is generated by the provider. Without this there is no way to know which JWK source to check for the key to verify the signature.

Copy link

Choose a reason for hiding this comment

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

@james-d-elliott, thank you for the clarification. That makes sense—since in JARM, the signature is generated by the provider, setting IsClientSigned to false ensures that the correct JWK source is used for verification.

(・_・?)


✏️ Learnings added
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#140
File: token/jwt/client.go:307-309
Timestamp: 2024-09-28T13:55:18.497Z
Learning: For `decoratedJARMClient`, the `IsClientSigned` method should return `false` because, in JARM, the signature is generated by the provider, not the client. This ensures the correct JWK source is used for verifying the signature.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +492 to +494
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) {
return false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Modify IsClientSigned to reflect client configuration in decoratedIntrospectionClient

In decoratedIntrospectionClient, the IsClientSigned method returns false. Since clients can specify a signing algorithm for introspection responses via GetIntrospectionSignedResponseAlg, consider modifying IsClientSigned to return true when a signing algorithm is set.

Apply this diff to adjust IsClientSigned accordingly:

func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) {
-	return false
+	return r.GetIntrospectionSignedResponseAlg() != ""
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) {
return false
}
func (r *decoratedIntrospectionClient) IsClientSigned() (is bool) {
return r.GetIntrospectionSignedResponseAlg() != ""
}

Comment on lines +367 to +369
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) {
return false
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Modify IsClientSigned to reflect client configuration in decoratedUserInfoClient

Currently, IsClientSigned in decoratedUserInfoClient always returns false. Since clients may specify a signing algorithm for UserInfo responses via GetUserinfoSignedResponseAlg, consider altering IsClientSigned to return true when a signing algorithm is configured.

Apply this diff to conditionally set IsClientSigned:

func (r *decoratedUserInfoClient) IsClientSigned() (is bool) {
-	return false
+	return r.GetUserinfoSignedResponseAlg() != ""
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) {
return false
}
func (r *decoratedUserInfoClient) IsClientSigned() (is bool) {
return r.GetUserinfoSignedResponseAlg() != ""
}

Copy link

@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: 17

🧹 Outside diff range and nitpick comments (37)
token/jwt/variables.go (4)

6-6: Add documentation for MarshalSingleStringAsArray.

While the variable name is descriptive, its purpose and potential impact on JWT operations are not immediately clear. Consider adding a comment explaining:

  1. The specific use case for this variable.
  2. The implications of setting it to true or false.
  3. Any compatibility considerations with other JWT libraries or standards.

This documentation will help other developers understand when and how to use or modify this setting.


7-7: LGTM: TimePrecision set to time.Second.

Setting TimePrecision to time.Second is a reasonable default for most JWT use cases. It aligns well with the standard practice of using Unix timestamps in JWT claims.

Consider adding a brief comment explaining the purpose of this variable and noting that it can be modified if higher precision is required for specific use cases.


8-8: LGTM: TimeFunc set to time.Now.

Setting TimeFunc to time.Now is a good practice. It allows for easy customization and mocking of time-related functionality, which is particularly useful for testing.

Consider adding a brief comment explaining the purpose of this variable and how it can be used in testing scenarios or for custom time handling.


5-9: Consider alternative configuration approaches and add comprehensive documentation.

The use of package-level variables for configuration is a common pattern in Go. However, for a JWT library, consider if this approach provides the best flexibility and clarity for users of the package. Some alternatives to consider:

  1. Use a configuration struct that can be passed to relevant functions or methods.
  2. Implement these as fields in a JWT handler or processor struct.

These approaches could provide more explicit configuration and potentially better support for concurrent usage with different settings.

Regardless of the chosen approach, it's crucial to add comprehensive documentation for these configuration options. This documentation should include:

  1. The purpose of each variable.
  2. The impact of changing these variables on JWT processing.
  3. Any interactions between these variables and other parts of the JWT system.
  4. Examples of when and how to modify these variables.

Clear documentation will greatly improve the usability and maintainability of the JWT package.

token/jwt/claims_jarm_test.go (1)

Inconsistency Found: consts.Claim* references still present across the codebase.

The verification revealed that consts.Claim* references remain in multiple files, indicating that the refactoring is not fully consistent. Please ensure that all instances of consts.Claim* are replaced with direct Claim* constants throughout the codebase to maintain clarity and reduce dependencies.

🔗 Analysis chain

Line range hint 28-43: Overall LGTM! Consistent removal of consts package dependency.

The changes in this file consistently replace consts.Claim* references with direct Claim* constants. This improves code clarity and reduces external dependencies without altering functionality. Great job!

To ensure consistency across the codebase, please run the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that `consts.Claim*` references have been removed throughout the codebase.

# Test: Search for any remaining `consts.Claim` references. Expect: No results.
rg --type go 'consts\.Claim'

# Test: Search for direct `Claim` constant usage. Expect: Multiple results.
rg --type go '\bClaim(Issuer|Audience|JWTID|IssuedAt|ExpirationTime)\b'

Length of output: 68335

token/jwt/claims.go (2)

10-19: LGTM! Consider minor enhancements to the Claims interface.

The Claims interface is well-designed and covers all standard JWT claims. It provides methods for accessing individual claims, converting to map claims, and validating the claims.

Consider the following enhancements:

  1. Add a method to retrieve all claims at once, which could be useful in some scenarios:

    GetAllClaims() (MapClaims, error)
  2. Improve documentation for the Valid method, especially regarding the purpose and usage of ClaimValidationOption:

    // Valid validates the claims according to the JWT specification and any provided options.
    // ClaimValidationOption allows for custom validation logic to be applied.
    Valid(opts ...ClaimValidationOption) error

These suggestions aim to improve the interface's usability and clarity.


Line range hint 22-121: Consider improving error handling in utility functions.

While not part of the current changes, the existing utility functions (ToString, ToTime, Filter, Copy, and StringSliceFromMap) could benefit from improved error handling.

Consider the following improvements:

  1. In ToTime, add error return for invalid input types:

    func ToTime(i any) (time.Time, error) {
        // ... existing code ...
        return time.Time{}, fmt.Errorf("unsupported type for time conversion: %T", i)
    }
  2. In StringSliceFromMap, consider returning an error instead of a boolean for invalid types:

    func StringSliceFromMap(value any) ([]string, error) {
        // ... existing code ...
        return nil, fmt.Errorf("unsupported type for string slice conversion: %T", value)
    }

These changes would make the functions more robust and easier to use, as callers would have more information about why a conversion failed.

token/jwt/claims_id_token_test.go (1)

Line range hint 58-80: Consider completing the refactoring for consistency.

While the earlier part of this test has been updated to use direct constant references, this segment still uses the consts. prefix for claim constants. For consistency and to complete the refactoring:

  1. Replace all remaining consts.Claim* references with their direct Claim* counterparts.
  2. Update the consts.ClaimNonce reference similarly.

This will ensure consistency throughout the test and align with the changes made in the rest of the file.

Here's a suggested diff to complete the refactoring:

-		consts.ClaimJWTID:              idTokenClaims.JTI,
-		consts.ClaimSubject:            idTokenClaims.Subject,
-		consts.ClaimIssuedAt:           idTokenClaims.IssuedAt.Unix(),
-		consts.ClaimIssuer:             idTokenClaims.Issuer,
-		consts.ClaimAudience:           idTokenClaims.Audience,
-		consts.ClaimExpirationTime:     idTokenClaims.ExpiresAt.Unix(),
+		ClaimJWTID:              idTokenClaims.JTI,
+		ClaimSubject:            idTokenClaims.Subject,
+		ClaimIssuedAt:           idTokenClaims.IssuedAt.Unix(),
+		ClaimIssuer:             idTokenClaims.Issuer,
+		ClaimAudience:           idTokenClaims.Audience,
+		ClaimExpirationTime:     idTokenClaims.ExpiresAt.Unix(),
 		"foo":                          idTokenClaims.Extra["foo"],
 		"baz":                          idTokenClaims.Extra["baz"],
-		consts.ClaimAccessTokenHash:    idTokenClaims.AccessTokenHash,
-		consts.ClaimCodeHash:           idTokenClaims.CodeHash,
-		consts.ClaimStateHash:          idTokenClaims.StateHash,
-		consts.ClaimAuthenticationTime: idTokenClaims.AuthTime.Unix(),
-		consts.ClaimAuthenticationContextClassReference: idTokenClaims.AuthenticationContextClassReference,
-		consts.ClaimAuthenticationMethodsReference:      idTokenClaims.AuthenticationMethodsReferences,
-		consts.ClaimNonce: idTokenClaims.Nonce,
+		ClaimAccessTokenHash:    idTokenClaims.AccessTokenHash,
+		ClaimCodeHash:           idTokenClaims.CodeHash,
+		ClaimStateHash:          idTokenClaims.StateHash,
+		ClaimAuthenticationTime: idTokenClaims.AuthTime.Unix(),
+		ClaimAuthenticationContextClassReference: idTokenClaims.AuthenticationContextClassReference,
+		ClaimAuthenticationMethodsReference:      idTokenClaims.AuthenticationMethodsReferences,
+		ClaimNonce: idTokenClaims.Nonce,
token/hmac/hmacsha_test.go (1)

Line range hint 68-76: Approved with a suggestion for improved error reporting.

The simplification of the loop is good for code cleanliness. However, to maintain ease of debugging, consider using t.Run with subtests for each case. This approach provides better error reporting without adding noise to the test output.

Here's a suggested refactoring to improve error reporting:

-for _, c := range []string{
-	"",
-	" ",
-	"foo.bar",
-	"foo.",
-	".foo",
-} {
-	err = cg.Validate(context.Background(), c)
-	assert.Error(t, err)
-}
+testCases := []string{"", " ", "foo.bar", "foo.", ".foo"}
+for i, c := range testCases {
+	t.Run(fmt.Sprintf("Case%d", i), func(t *testing.T) {
+		err = cg.Validate(context.Background(), c)
+		assert.Error(t, err)
+	})
+}

This change will provide more detailed error output if a test case fails, without adding unnecessary logging for successful cases.

token/jwt/claims_jwt.go (3)

104-155: LGTM with a minor suggestion for consistency

The changes in the ToMap method look good. The switch from consts.Claim* to Claim* is consistent throughout the method.

For consistency, consider updating line 118 to use ClaimJWTID instead of a string literal:

-		ret[ClaimJWTID] = uuid.New().String()
+		ret[ClaimJWTID] = uuid.New().String()

This change would align with the usage of Claim* constants in the rest of the method.


229-239: LGTM with a minor suggestion

The changes to the toTime function improve its robustness by handling more input types and providing a success indicator. This is a good improvement.

Consider adding a comment explaining the meaning of the ok return value:

// toTime converts the input to a time.Time value.
// It returns the converted time and a boolean indicating whether the conversion was successful.
func toTime(v any, def time.Time) (t time.Time, ok bool) {
    // ... (rest of the function)
}

This will make the function's behavior clearer to other developers.


270-302: LGTM with suggestions for improved error handling

The toNumericDate function is a good addition, providing consistent handling of various numeric types for date conversion. The handling of zero values and json.Number is particularly noteworthy.

Consider the following improvements for more consistent error handling:

  1. Return an error for unsupported types instead of using a catch-all error at the end.
  2. Handle potential errors from json.Number.Float64().

Here's a suggested refactor:

func toNumericDate(v any) (*NumericDate, error) {
    switch value := v.(type) {
    case float64, int64, int32, int:
        if value == 0 {
            return nil, nil
        }
        return newNumericDateFromSeconds(float64(value)), nil
    case json.Number:
        f, err := value.Float64()
        if err != nil {
            return nil, fmt.Errorf("invalid json.Number: %w", err)
        }
        if f == 0 {
            return nil, nil
        }
        return newNumericDateFromSeconds(f), nil
    default:
        return nil, fmt.Errorf("unsupported type for NumericDate: %T", v)
    }
}

This refactored version provides more specific error messages and handles potential errors from json.Number.Float64().

token/jwt/claims_map.go (6)

69-131: LGTM: Comprehensive handling of audience claims with a minor optimization opportunity

The new methods for handling audience claims are well-implemented, covering various scenarios (single audience, any audience, all audiences). The use of constant-time comparison in the helper functions is a good security practice.

Consider combining the VerifyAudienceAll and VerifyAudienceAny methods into a single method with a boolean parameter to determine the verification type. This could reduce code duplication and improve maintainability. For example:

func (m MapClaims) VerifyAudiences(cmp []string, required bool, allRequired bool) bool {
    // ... implementation ...
}

This suggestion is optional and depends on the specific use cases in your project.


133-202: LGTM: Consistent handling of time-based claims with a minor improvement suggestion

The new methods for handling time-based claims (expiration time, issued at, and not before) are well-implemented. They correctly handle cases where claims are not set and use appropriate helper functions for verification.

For consistency with other claim verification methods, consider renaming the helper functions to match the naming pattern:

-func verifyInt64Future(value, now int64, required bool) bool {
+func verifyExpirationTime(value, now int64, required bool) bool {
    // ... implementation ...
}

-func verifyInt64Past(value, now int64, required bool) bool {
+func verifyIssuedAtOrNotBefore(value, now int64, required bool) bool {
    // ... implementation ...
}

This change would make the helper function names more descriptive and consistent with the claim they're verifying.


217-286: LGTM: Comprehensive claim validation with a suggestion for improved error handling

The new Valid method provides a thorough and flexible approach to validating all claims. It correctly handles various validation scenarios and accumulates multiple validation errors.

Consider enhancing the error handling to provide more detailed information about which claims failed validation. This could be achieved by modifying the ValidationError struct to include a map of claim-specific error messages. For example:

type ValidationError struct {
    Errors     uint32
    Inner      error
    ClaimErrors map[string]string
}

// In the Valid method:
if !m.VerifyExpirationTime(now, vopts.expRequired) {
    vErr.ClaimErrors["exp"] = "Token is expired"
    vErr.Errors |= ValidationErrorExpired
}
// ... similar for other claims ...

// Modify the Error() method of ValidationError to include claim-specific messages

This change would provide more detailed error information, making it easier for users of this library to understand and handle validation failures.


365-424: LGTM: Well-implemented claim validation options with a suggestion for improved documentation

The new ClaimValidationOption type and related functions provide a flexible and extensible way to configure claim validation. The use of the functional options pattern is a good choice for this scenario.

Consider adding godoc comments to each ValidateX function to provide more context on their usage. For example:

// ValidateTimeFunc sets a custom time function for claim validation.
// If not set, the default time.Now will be used.
func ValidateTimeFunc(timef func() time.Time) ClaimValidationOption {
    // ... implementation ...
}

// ValidateIssuer sets the required issuer for claim validation.
func ValidateIssuer(iss string) ClaimValidationOption {
    // ... implementation ...
}

// ... similar for other functions ...

This addition would improve the discoverability and usability of these options for users of your library.


425-498: LGTM: Well-implemented helper functions with a suggestion for improved organization

The helper functions for claim verification are correctly implemented. The use of constant-time comparison in audience verification functions is a good security practice. The time-based verification functions are simple and handle the case where the claim is not set.

Consider grouping related helper functions together for better code organization. For example:

// Audience verification helpers
func verifyAud(aud []string, cmp string, required bool) bool {
    // ... implementation ...
}

func verifyAudAny(aud []string, cmp []string, required bool) bool {
    // ... implementation ...
}

func verifyAudAll(aud []string, cmp []string, required bool) bool {
    // ... implementation ...
}

// Time-based verification helpers
func verifyInt64Future(value, now int64, required bool) bool {
    // ... implementation ...
}

func verifyInt64Past(value, now int64, required bool) bool {
    // ... implementation ...
}

// String verification helper
func verifyString(value, cmp string, required bool) bool {
    // ... implementation ...
}

This organization would improve the readability and maintainability of the code.


Line range hint 1-498: Excellent enhancements to JWT claim handling and validation

The changes in this file significantly improve the functionality and flexibility of the MapClaims type for JWT handling. Key improvements include:

  1. Comprehensive methods for getting and verifying individual claims.
  2. A flexible Valid method for validating multiple claims with customizable options.
  3. Secure implementation using constant-time comparisons where necessary.
  4. Well-structured code following consistent patterns.

These enhancements will make it easier and safer for users of this library to work with JWTs, providing both fine-grained control over individual claims and high-level validation of the entire token.

As the JWT functionality grows, consider splitting this file into multiple files for better maintainability. For example:

  • claims_map.go: Core MapClaims type and methods
  • claims_validation.go: Validation logic and options
  • claims_helpers.go: Helper functions for claim verification

This separation would improve code organization and make it easier to navigate and maintain the codebase as it continues to evolve.

token/jwt/claims_map_test.go (4)

389-461: LGTM: Comprehensive test cases for VerifyIssuer

The test cases for TestMapClaims_VerifyIssuer are well-structured and cover a wide range of scenarios, including:

  • Valid and invalid inputs
  • Different data types
  • Edge cases (nil values, wrong types)
  • Required and non-required issuer checks

Consider adding a test case for an empty string issuer:

{
    "ShouldFailEmptyString",
    MapClaims{
        consts.ClaimIssuer: "",
    },
    "foo",
    true,
    false,
},

This additional test case would ensure that an empty string is not considered a valid issuer when a specific issuer is required.


465-528: LGTM: Thorough test cases for VerifySubject

The test cases for TestMapClaims_VerifySubject are well-designed and cover a wide range of scenarios, including:

  • Valid and invalid inputs
  • Different data types
  • Edge cases (nil values, wrong types)
  • Required and non-required subject checks

Consider adding a test case for an empty string subject:

{
    "ShouldFailEmptyString",
    MapClaims{
        consts.ClaimSubject: "",
    },
    "foo",
    true,
    false,
},

This additional test case would ensure that an empty string is not considered a valid subject when a specific subject is required.


532-613: LGTM: Comprehensive test cases for VerifyExpiresAt

The test cases for TestMapClaims_VerifyExpiresAt are well-structured and cover a wide range of scenarios, including:

  • Valid and invalid inputs
  • Different data types (int64, int, int32)
  • Edge cases (nil values, wrong types)
  • Required and non-required expiration checks

Consider adding test cases for time-based comparisons:

{
    "ShouldPassFuture",
    MapClaims{
        consts.ClaimExpirationTime: int64(200),
    },
    int64(100),
    true,
    true,
},
{
    "ShouldFailPast",
    MapClaims{
        consts.ClaimExpirationTime: int64(100),
    },
    int64(200),
    true,
    false,
},

These additional test cases would ensure that the VerifyExpiresAt method correctly handles future and past expiration times relative to the comparison time.


805-966: LGTM with a suggestion for improved error checking

The test cases for TestMapClaims_Valid are comprehensive and cover a wide range of scenarios, including:

  • Various claim validations (expiration, issuance, not before, issuer, subject, audience)
  • Different combinations of claims and validation options
  • Error cases and expected error messages

Consider improving the error checking in the test function:

 var e *ValidationError
-errors.As(actual, &e)
+if errors.As(actual, &e) {
     var errs uint32
     for _, err := range tc.errs {
         errs |= err
         assert.True(t, e.Has(err))
     }
     assert.Equal(t, errs, e.Errors)
+} else {
+    t.Errorf("Expected ValidationError, but got a different error type")
+}

This change ensures that the code only accesses the ValidationError fields if the type assertion was successful, preventing potential nil pointer dereferences.

client_authentication_strategy.go (5)

Line range hint 29-67: LGTM! Enhanced client authentication flow.

The refactoring of the AuthenticateClient method improves the overall structure and readability of the authentication process. The introduction of the ClientAssertion type and the use of a handler instead of a resolver align well with the new JWT-based approach.

Consider adding a comment explaining the purpose of the ClientAssertion type for better code documentation.


137-184: LGTM! Enhanced client assertion handling.

The refactoring of NewClientAssertion significantly improves the handling of client assertions. The introduction of the jwt.Strategy parameter and the comprehensive JWT validation logic enhance the security and flexibility of the authentication process.

Consider adding error wrapping to provide more context when returning errors, especially for the error returned on line 161.


247-307: LGTM! Significantly enhanced JWT bearer token authentication.

The refactoring of doAuthenticateAssertionJWTBearer greatly improves the security and compliance of JWT-based client authentication. The additional checks for the AuthenticationMethodClient interface and parsed assertion, along with more detailed claim validation, are excellent improvements.

Consider extracting the claim validation logic (lines 277-282) into a separate method for better readability and maintainability.


310-343: LGTM! Comprehensive improvements to JWT parsing and validation.

The refactoring of doAuthenticateAssertionParseAssertionJWTBearer significantly enhances the security and compliance of JWT parsing and validation. The additional checks for audience, expiration, and various header validations are excellent improvements that align with best practices for JWT handling in OAuth 2.0 and OpenID Connect.

Consider enhancing the error handling to provide more context-specific error messages. This could be achieved by wrapping the errors returned by the JWT library with more descriptive messages related to client authentication.


387-462: LGTM! Detailed client assertion error formatting.

The new fmtClientAssertionDecodeError function significantly enhances error reporting for client assertion decoding issues. It provides very detailed and context-specific error messages for a wide range of JWT validation failure scenarios, which will greatly aid in diagnosing and troubleshooting client assertion problems.

Consider adding comments to group related error cases (e.g., header-related errors, claim-related errors) to improve readability of this large switch statement.

token/jwt/jwt_strategy_test.go (2)

514-558: Well-structured test for decoding encrypted tokens with room for improvement.

This test function is well-designed:

  1. It uses table-driven tests, which is a good practice for testing multiple scenarios efficiently.
  2. It covers different encryption algorithms (RS256 and ES256).
  3. It includes validation of various JWT claims.

Suggestions for improvement:

  1. Consider adding more comprehensive error checking. Currently, the test only checks if there's no error and if the token is not nil. It would be beneficial to add specific checks for expected errors in failure scenarios.
  2. Add test cases for invalid tokens or incorrect keys to ensure the decoding fails as expected in these cases.

Example of additional error checking:

if err != nil {
    t.Fatalf("Failed to decode token: %v", err)
}
if token == nil {
    t.Fatal("Decoded token is nil")
}

Adding these improvements will make the tests more robust and comprehensive.


1-724: Overall assessment: Comprehensive test suite with room for improvement

This test file for the JWT strategy implementation is thorough and covers many important scenarios. Here's a summary of the strengths and areas for improvement:

Strengths:

  1. Comprehensive coverage of various JWT operations (encoding, decoding, encryption).
  2. Good use of table-driven tests in some functions.
  3. Proper setup of test keys and helper structures.

Areas for improvement:

  1. Some test functions (e.g., TestDefaultStrategy, TestNestedJWTEncodeDecode) are very long and could be broken down into smaller, more focused tests.
  2. The TestIniit function needs significant rework to follow proper testing practices.
  3. Some tests could benefit from more comprehensive error checking and edge case coverage.

Recommendations:

  1. Refactor long test functions into smaller, more focused tests.
  2. Improve the TestIniit function by fixing the name, adding proper assertions, and clarifying its purpose.
  3. Add more error checking and edge cases to existing tests where appropriate.
  4. Consider adding more comments to explain the purpose and expected outcomes of complex test scenarios.

Implementing these improvements will enhance the maintainability and effectiveness of the test suite.

token/jwt/date.go (1)

47-47: Typo in error message: "NumericData" should be "NumericDate"

There's a typo in the error message. It should read "could not parse NumericDate" instead of "could not parse NumericData".

Apply this diff to fix the typo:

 func (date *NumericDate) UnmarshalJSON(b []byte) (err error) {
     var (
         number json.Number
         f      float64
     )

     if err = json.Unmarshal(b, &number); err != nil {
-        return fmt.Errorf("could not parse NumericData: %w", err)
+        return fmt.Errorf("could not parse NumericDate: %w", err)
     }
token/jwt/consts.go (6)

24-25: Clarify variable name SignatureAlgorithmsNone for better understanding.

The variable SignatureAlgorithmsNone includes all algorithms including 'none', as per the comment. The name may be misleading. Consider renaming it to SignatureAlgorithmsIncludingNone or AllSignatureAlgorithms for clarity.


36-68: Add documentation comments for exported constants.

The constants from lines 37 to 68 are exported but lack documentation comments. According to Go conventions, exported identifiers should have comments to explain their purpose. Consider adding comments to these constants for better code readability and maintainability.


71-79: Add documentation comments for exported constants.

The constants from lines 71 to 79 are exported but missing documentation comments. To adhere to Go best practices, please add comments to these constants.


82-84: Add documentation comments for exported constants.

The exported constants on lines 82 to 84 lack documentation comments. Adding comments will improve code clarity and maintain Go documentation standards.


87-91: Add documentation comments for exported constants.

The constants from lines 87 to 91 are exported without accompanying documentation. Please provide comments to describe their purpose.


94-94: Add documentation comment for exported constant JSONWebTokenAlgNone.

The exported constant JSONWebTokenAlgNone lacks a documentation comment. Adding a comment will enhance code readability and adhere to Go best practices.

authorize_request_handler.go (1)

75-75: Unnecessary break Statement in Switch Case

In Go, break statements at the end of a switch case are not necessary because control flow does not fall through by default. Removing the break statement can simplify the code.

Apply this diff to remove the unnecessary break statement:

	case consts.JSONWebTokenAlgNone:
-		break
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 5df0ecf and 1438817.

📒 Files selected for processing (51)
  • arguments_test.go (0 hunks)
  • authorize_helper_test.go (0 hunks)
  • authorize_request_handler.go (6 hunks)
  • authorize_request_handler_oidc_request_test.go (9 hunks)
  • authorize_response_writer_test.go (0 hunks)
  • client_authentication_strategy.go (8 hunks)
  • handler/oauth2/introspector_jwt.go (1 hunks)
  • handler/oauth2/strategy_jwt_profile.go (4 hunks)
  • handler/openid/flow_explicit_token_test.go (3 hunks)
  • handler/openid/flow_refresh_token_test.go (4 hunks)
  • handler/openid/helper_test.go (1 hunks)
  • handler/openid/strategy_jwt.go (4 hunks)
  • handler/openid/validator.go (2 hunks)
  • handler/openid/validator_test.go (2 hunks)
  • handler/rfc8628/token_endpoint_handler_test.go (0 hunks)
  • handler/rfc8693/custom_jwt_type_handler.go (4 hunks)
  • handler/rfc8693/token_exchange_test.go (4 hunks)
  • helper_test.go (0 hunks)
  • integration/authorize_code_grant_public_client_pkce_test.go (0 hunks)
  • integration/client_credentials_grant_test.go (0 hunks)
  • integration/introspect_token_test.go (2 hunks)
  • integration/resource_owner_password_credentials_grant_test.go (0 hunks)
  • introspection_response_writer.go (2 hunks)
  • pushed_authorize_response_writer_test.go (0 hunks)
  • rfc8628_device_authorize_response_writer_test.go (0 hunks)
  • rfc8628_user_authorize_request_handler_test.go (0 hunks)
  • rfc8628_user_authorize_response_writer_test.go (0 hunks)
  • session.go (1 hunks)
  • testing/mock/client.go (2 hunks)
  • token/hmac/hmacsha_test.go (1 hunks)
  • token/jarm/generate.go (1 hunks)
  • token/jwt/claims.go (1 hunks)
  • token/jwt/claims_id_token.go (2 hunks)
  • token/jwt/claims_id_token_test.go (2 hunks)
  • token/jwt/claims_jarm.go (2 hunks)
  • token/jwt/claims_jarm_test.go (2 hunks)
  • token/jwt/claims_jwt.go (4 hunks)
  • token/jwt/claims_jwt_test.go (4 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/claims_map_test.go (1 hunks)
  • token/jwt/consts.go (1 hunks)
  • token/jwt/date.go (1 hunks)
  • token/jwt/header.go (1 hunks)
  • token/jwt/issuer.go (1 hunks)
  • token/jwt/jwt_strategy.go (1 hunks)
  • token/jwt/jwt_strategy_opts.go (1 hunks)
  • token/jwt/jwt_strategy_test.go (1 hunks)
  • token/jwt/token.go (3 hunks)
  • token/jwt/token_test.go (8 hunks)
  • token/jwt/util.go (1 hunks)
  • token/jwt/variables.go (1 hunks)
💤 Files not reviewed due to no reviewable changes (12)
  • arguments_test.go
  • authorize_helper_test.go
  • authorize_response_writer_test.go
  • handler/rfc8628/token_endpoint_handler_test.go
  • helper_test.go
  • integration/authorize_code_grant_public_client_pkce_test.go
  • integration/client_credentials_grant_test.go
  • integration/resource_owner_password_credentials_grant_test.go
  • pushed_authorize_response_writer_test.go
  • rfc8628_device_authorize_response_writer_test.go
  • rfc8628_user_authorize_request_handler_test.go
  • rfc8628_user_authorize_response_writer_test.go
🚧 Files skipped from review as they are similar to previous changes (21)
  • authorize_request_handler_oidc_request_test.go
  • handler/oauth2/introspector_jwt.go
  • handler/oauth2/strategy_jwt_profile.go
  • handler/openid/flow_explicit_token_test.go
  • handler/openid/flow_refresh_token_test.go
  • handler/openid/helper_test.go
  • handler/openid/strategy_jwt.go
  • handler/openid/validator.go
  • handler/openid/validator_test.go
  • handler/rfc8693/custom_jwt_type_handler.go
  • handler/rfc8693/token_exchange_test.go
  • integration/introspect_token_test.go
  • introspection_response_writer.go
  • testing/mock/client.go
  • token/jarm/generate.go
  • token/jwt/claims_jarm.go
  • token/jwt/issuer.go
  • token/jwt/jwt_strategy.go
  • token/jwt/jwt_strategy_opts.go
  • token/jwt/token_test.go
  • token/jwt/util.go
🧰 Additional context used
📓 Learnings (1)
authorize_request_handler.go (1)
Learnt from: james-d-elliott
PR: authelia/oauth2-provider#140
File: authorize_request_handler.go:121-125
Timestamp: 2024-09-27T07:36:36.699Z
Learning: In `authorize_request_handler.go`, only clients explicitly configured to allow the `'none'` algorithm can use it, and validation is performed appropriately (e.g., at line 140).
🔇 Additional comments (36)
token/jwt/variables.go (1)

1-3: LGTM: Package declaration and import.

The package declaration and import statement are correct and appropriate for the file's purpose.

token/jwt/claims_jarm_test.go (2)

28-34: LGTM! Improved code clarity by removing consts package dependency.

The direct use of claim constants (ClaimIssuer, ClaimAudience, etc.) instead of consts.Claim* improves code readability and reduces external dependencies. This change is consistent and doesn't alter the functionality.


43-43: LGTM! Consistent change in test assertion.

The replacement of consts.ClaimJWTID with ClaimJWTID is consistent with the earlier changes. This modification maintains the test's functionality while improving code clarity.

session.go (2)

22-23: Review impact of interface method renaming

The inconsistency between the documentation and the method signature in the Session interface could have broader implications:

  1. If the rename to GetExpiresTimeX was intended:

    • All implementations of the Session interface need to be updated.
    • All usages of this method throughout the codebase need to be modified.
    • This change would likely cause compilation errors until all occurrences are updated.
  2. If the rename was unintended:

    • The documentation comment should be reverted to maintain consistency.

Recommendation:

  1. Clarify the intention behind this change.
  2. If the rename is desired, ensure a comprehensive update across the entire codebase.
  3. If not intended, revert the documentation change.
  4. Consider using an IDE or tool-assisted refactoring to ensure all occurrences are updated consistently if proceeding with the rename.

To assess the scope of this change, run the following script to find all implementations and usages of the Session interface:

#!/bin/bash
echo "Searching for Session interface implementations:"
rg --type go 'type\s+\w+\s+struct\s*{[\s\S]*?}\s*func\s*\(\w+\s*\*?\w+\)\s*GetExpiresAt'
echo "Searching for GetExpiresAt method calls:"
rg --type go '\.GetExpiresAt\s*\('

This will help in understanding the full impact of the potential rename and ensure no usages are overlooked.


22-23: ⚠️ Potential issue

Inconsistency between documentation and method signature

There's a mismatch between the documentation comment and the actual method signature. The comment refers to GetExpiresTimeX, but the method is still named GetExpiresAt.

To resolve this, either:

  1. Update the method signature to match the new name in the comment:
- GetExpiresAt(key TokenType) time.Time
+ GetExpiresTimeX(key TokenType) time.Time
  1. Or, revert the comment to use the original method name:
- //  session.GetExpiresTimeX(oauth2.AccessToken)
+ //  session.GetExpiresAt(oauth2.AccessToken)

Please clarify the intended change and update both the comment and method signature consistently.

To verify the usage of this method across the codebase, run:

This will help determine which method name is being used in other parts of the codebase.

token/jwt/claims_jwt_test.go (4)

33-42: LGTM: Improved readability by removing consts. prefixes

The changes to jwtClaimsMap look good. Removing the consts. prefixes from claim constants improves code readability without affecting functionality. This change is consistent across all claim constants and aligns with the expected modifications mentioned in the AI-generated summary.


51-51: LGTM: Consistent removal of consts. prefix

The change in the TestClaimsToMapSetsID function is consistent with the modifications made to jwtClaimsMap. Removing the consts. prefix from ClaimJWTID maintains code consistency without altering the test's functionality.


81-82: LGTM: Consistent changes across test functions

The modifications in TestScopeFieldString and TestScopeFieldBoth functions are consistent with the earlier changes. Removing the consts. prefix from claim constants maintains code consistency and improves readability without altering the tests' functionalities. These changes align well with the overall refactoring described in the AI-generated summary.

Also applies to: 93-93


Line range hint 1-98: Overall assessment: Consistent and beneficial refactoring

The changes made to this file consistently remove the consts. prefix from claim constants throughout the code. This refactoring improves readability and reduces verbosity without altering the functionality of the tests. The modifications align well with the description provided in the AI-generated summary and contribute to a more streamlined codebase.

token/jwt/claims_id_token_test.go (2)

46-57: LGTM! Verify all constant origins.

The changes from consts.Claim* to Claim* are consistent with the refactoring described in the summary. The test logic and expected values remain unchanged, and the readability is improved by reducing prefix usage.

To ensure all constants are correctly defined, run the following script:

#!/bin/bash
# Description: Verify the origin of all Claim* constants used in this test

# Test: Search for all Claim* constant definitions
rg --type go -A 5 'const\s+(Claim\w+)'

22-22: LGTM! Verify constant origin.

The change from consts.ClaimJWTID to ClaimJWTID is consistent with the refactoring described in the summary. The test logic remains valid.

To ensure the constant is correctly defined, run the following script:

✅ Verification successful

Constant ClaimJWTID correctly defined and imported.

The refactoring from consts.ClaimJWTID to ClaimJWTID is valid. The constant is properly defined in internal/consts/claim.go and correctly imported in the test file through the dot import of the jwt package. The test logic remains intact and functions as expected.

  • internal/consts/claim.go: Defines ClaimJWTID = "jti"
  • token/jwt/consts.go: Aliases ClaimJWTID = consts.ClaimJWTID
  • token/jwt/claims_id_token_test.go: Uses ClaimJWTID directly
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the origin of the ClaimJWTID constant

# Test: Search for the ClaimJWTID constant definition
rg --type go -A 5 'const\s+ClaimJWTID'

Length of output: 40


Script:

#!/bin/bash
# Description: Broadly search for the ClaimJWTID identifier across the codebase

# Test: Search for any occurrence of ClaimJWTID in Go files
rg --type go 'ClaimJWTID'

Length of output: 3073

token/jwt/claims_id_token.go (2)

7-8: LGTM: New imports and method declaration look good.

The new imports are appropriate for the UnmarshalJSON method implementation. The method signature correctly follows the json.Unmarshaler interface.

Also applies to: 11-11, 15-15, 37-48


123-123: LGTM: Minor updates to ToMap method look good.

The changes to use ClaimSubject and ClaimIssuer constants instead of consts.ClaimSubject and consts.ClaimIssuer are consistent with the rest of the code and may improve readability.

Also applies to: 125-125, 129-129

token/jwt/claims_jwt.go (2)

241-268: LGTM: Robust helper function for numeric conversions

The toInt64 function is a well-implemented helper that handles various numeric types and provides proper error handling. This addition enhances the overall robustness of the time conversion process.

Good job on including handling for json.Number, which can be particularly tricky. The function correctly attempts both Int64() and Float64() conversions for this type.


187-191: ⚠️ Potential issue

Handle the success/failure status returned by toTime

The current implementation ignores the second return value from toTime, which indicates whether the conversion was successful. This could lead to silent failures if the time conversion fails.

Consider handling the success status to ensure data integrity:

-c.IssuedAt, _ = toTime(v, c.IssuedAt)
+c.IssuedAt, ok := toTime(v, c.IssuedAt)
+if !ok {
+    // Handle conversion failure (e.g., log warning, set to default value, etc.)
+}

-c.NotBefore, _ = toTime(v, c.NotBefore)
+c.NotBefore, ok := toTime(v, c.NotBefore)
+if !ok {
+    // Handle conversion failure
+}

-c.ExpiresAt, _ = toTime(v, c.ExpiresAt)
+c.ExpiresAt, ok := toTime(v, c.ExpiresAt)
+if !ok {
+    // Handle conversion failure
+}

This approach will allow you to handle cases where time conversion fails, improving the robustness of your code.

token/jwt/claims_map.go (1)

21-67: LGTM: Consistent and secure implementation of issuer and subject claim methods

The new methods for getting and verifying issuer and subject claims are well-implemented. They follow a consistent pattern, handle cases where claims are not set, and use constant-time comparison for string verification, which is a good security practice to prevent timing attacks.

token/jwt/claims_map_test.go (4)

17-127: LGTM: Comprehensive test cases for VerifyAudience

The test cases for TestMapClaims_VerifyAudience are well-structured and cover a wide range of scenarios, including:

  • Valid and invalid inputs
  • Different data types (string, slice of strings, any)
  • Edge cases (empty claims, nil values)
  • Required and non-required audience checks

This comprehensive approach ensures robust testing of the VerifyAudience method.


129-257: LGTM: Thorough test cases for VerifyAudienceAll

The test cases for TestMapClaims_VerifyAudienceAll are well-designed and cover a wide range of scenarios, including:

  • Single and multiple audience values
  • Different data types (string, slice of strings, any)
  • Edge cases (empty claims, nil values)
  • Required and non-required audience checks

This thorough approach ensures comprehensive testing of the VerifyAudienceAll method.


617-707: LGTM: Thorough test cases for VerifyIssuedAt

The test cases for TestMapClaims_VerifyIssuedAt are well-designed and cover a wide range of scenarios, including:

  • Valid and invalid inputs
  • Different data types (int64, int, int32)
  • Edge cases (nil values, wrong types)
  • Required and non-required issuance time checks
  • Future and past issuance times

This comprehensive approach ensures robust testing of the VerifyIssuedAt method.


Line range hint 1-966: Overall: Well-structured and comprehensive test suite

The claims_map_test.go file contains a robust and comprehensive set of unit tests for JWT claim verification methods. The test cases cover a wide range of scenarios, including valid and invalid inputs, different data types, and edge cases for various claim types (audience, issuer, subject, expiration time, issued at, and not before).

Key strengths:

  1. Thorough coverage of different claim types and validation scenarios
  2. Consistent structure across test functions
  3. Good use of table-driven tests for readability and maintainability

While some minor improvements were suggested, the overall quality of the test suite is high and provides strong confidence in the correctness of the JWT claim verification implementation.

client_authentication_strategy.go (6)

Line range hint 14-26: LGTM! Improved JWT handling and configuration.

The addition of the custom JWT package and the new methods in the Config interface enhance the JWT handling capabilities of the client authentication strategy. This change aligns well with the overall refactoring towards a more robust JWT management system.


Line range hint 70-134: LGTM! Improved flexibility in authentication methods.

The updates to the authenticate method, including the new assertion parameter and the switch case for handling assertions, enhance the method's ability to handle various authentication scenarios. This aligns well with the overall shift towards a more robust JWT-based authentication system.


Line range hint 186-193: LGTM! Improved ClientAssertion structure.

The updates to the ClientAssertion struct enhance its utility by providing more detailed information about the assertion. The new fields (Parsed, ID, Method, Algorithm) allow for better tracking and validation of the assertion throughout the authentication process.


346-349: LGTM! Simplified client credentials retrieval.

The refactoring of getClientCredentialsSecretPost simplifies the method and improves its readability. This change makes the code more maintainable and easier to understand.


352-384: LGTM! Comprehensive JWT error handling.

The new resolveJWTErrorToRFCError function greatly enhances the error handling for JWT-related issues. It provides detailed and context-specific error messages for various JWT validation scenarios, which will significantly improve debugging and troubleshooting of JWT validation problems.


Line range hint 1-462: Excellent refactoring of the client authentication strategy!

The changes in this file represent a significant improvement in the handling of JWT-based client authentication. Key improvements include:

  1. Enhanced JWT validation and parsing.
  2. More robust error handling with detailed, context-specific error messages.
  3. Better separation of concerns with the introduction of the jwt.Strategy.
  4. Improved security with constant-time comparisons for sensitive data.
  5. More comprehensive claim and header validations.

These changes align well with best practices for OAuth 2.0 and OpenID Connect, and should significantly improve the security, maintainability, and debuggability of the client authentication process.

token/jwt/jwt_strategy_test.go (4)

1-19: Imports and package declaration look good.

The package declaration and imports are appropriate for a JWT strategy test file. The necessary cryptographic and testing libraries are included.


291-326: Well-structured test for rejecting non-compact serialized JWTs.

This test function is well-designed:

  1. It uses table-driven tests, which is a good practice for testing multiple scenarios efficiently.
  2. It covers various edge cases, including empty strings and partially formed JWTs.
  3. The error message assertion is consistent across all test cases.

Good job on this test implementation.


560-596: Helper structures and functions are appropriate for testing.

The testConfig and testFetcher structures and their implementations are well-suited for the testing purposes of this file:

  1. testConfig provides a simple implementation of the GetJWKSFetcherStrategy method.
  2. testFetcher implements a basic JWKS fetcher, which is sufficient for testing scenarios.
  3. The Resolve method in testFetcher includes proper error handling and context usage.

These helper structures contribute to making the tests more manageable and focused on the JWT strategy implementation.


598-710: Global variables and init function are well-structured for test key setup.

The global variables and init function are appropriately used for setting up test keys:

  1. The use of global variables for test keys is acceptable in this testing context.
  2. The init function properly handles the parsing and setup of RSA and ECDSA keys for both signing and encryption.
  3. Error handling is implemented, with panics used appropriately for setup failures.
  4. The keys are set up with correct attributes (KeyID, Use, Algorithm) for both private and public keys.

This setup ensures that the necessary test keys are available for all test functions, promoting consistency across tests.

token/jwt/token.go (5)

616-631: Simplify isUnsafeNoneMagicConstant Function

The isUnsafeNoneMagicConstant function can be simplified by reducing nested type assertions, improving readability and maintainability. Currently, the function includes multiple type switches that can be consolidated.


350-440: Potential Overwriting of Errors in Header Validation

In the Valid method, the vErr.Inner field is overwritten each time a validation error is detected in different header fields. This means that if multiple validation errors occur, only the last error is reported, and earlier errors are lost. To provide comprehensive feedback, consider accumulating errors or appending messages to vErr.Inner.


490-518: Reduce Duplication in Header Validation Option Functions

The functions ValidateKeyID, ValidateAlgorithm, ValidateEncryptionKeyID, etc., have similar structures that could be refactored to reduce code duplication. Consolidating these functions can enhance maintainability.


583-591: ⚠️ Potential issue

Possible Panic in pointer Function with Nil Input

The pointer function can cause a panic if the input v is nil. Calling reflect.ValueOf(v).Kind() when v is nil will result in a runtime panic because reflect.ValueOf(nil) returns a zero Value, and calling Kind() on it is invalid. To prevent this, consider adding a nil check at the beginning of the function:

func pointer(v any) any {
+   if v == nil {
+       return nil
+   }
    if reflect.ValueOf(v).Kind() != reflect.Ptr {
        value := reflect.New(reflect.ValueOf(v).Type())
        value.Elem().Set(reflect.ValueOf(v))
        return value.Interface()
    }
    return v
}

This ensures that the function safely handles nil inputs without causing a panic.

Likely invalid or redundant comment.


209-215: Ensure SetJWE Handles Empty Parameters Appropriately

In the SetJWE method, ensure that it correctly handles cases where optional parameters like zip (compression algorithm) may be empty or unset. If any of these parameters are required for subsequent encryption processes, consider adding validation or default values to prevent potential runtime errors.

To verify that the SetJWE method correctly handles cases with empty parameters, you can run the following shell script to search for usages of SetJWE and check if it's being called with appropriate parameters throughout the codebase.

✅ Verification successful

Verified SetJWE Parameter Handling

The SetJWE method is consistently called with valid zip parameters across the codebase. Currently, all usages provide a defined compression algorithm, ensuring proper handling without runtime errors.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that `SetJWE` is called with appropriate parameters.

# Test: Search for usages of `SetJWE` in the codebase.
# Expect: Calls to `SetJWE` should provide valid arguments.
rg --type go --no-heading --line-number 'SetJWE\('

Length of output: 324


Script:

#!/bin/bash
# Description: Extract the implementation of the `SetJWE` method for analysis.

ast-grep --lang go --pattern 'func (t \*Token) SetJWE($_) { $$$ }' token/jwt/token.go

Length of output: 87


Script:

#!/bin/bash
# Description: Extract the implementation of the `SetJWE` method for analysis using a more flexible pattern.

ast-grep --lang go --pattern 'func (t \*Token) SetJWE($_) { $$$ }' token/jwt/token.go --debug

Length of output: 342


Script:

#!/bin/bash
# Description: Extract the implementation of the `SetJWE` method for analysis without using the unsupported --debug flag.

ast-grep --lang go --pattern 'func (t *Token) SetJWE($_) { $$$ }' token/jwt/token.go

Length of output: 86


Script:

#!/bin/bash
# Description: Extract the full implementation of the `SetJWE` method for analysis using `rg`.

# Use ripgrep to find the function definition and include the function body.
rg -A 20 -B 5 'func \(t \*Token\) SetJWE\(' token/jwt/token.go

Length of output: 756

authorize_request_handler.go (1)

522-598: Refactored Error Handling Enhances Maintainability

Great job extracting fmtRequestObjectDecodeError into a separate function. This refactoring centralizes error formatting for JWT decoding issues, improving readability and maintainability of the code.

@@ -18,7 +14,7 @@ func NewHeaders() *Headers {

// ToMap will transform the headers to a map structure
func (h *Headers) ToMap() map[string]any {
var filter = map[string]bool{consts.JSONWebTokenHeaderAlgorithm: true}
var filter = map[string]bool{JSONWebTokenHeaderAlgorithm: true}
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Issue Detected with JSONWebTokenHeaderAlgorithm Usage

The JSONWebTokenHeaderAlgorithm constant is used in token/jwt/header.go without importing the consts package. This will lead to an undefined variable error.

Action Required:

  • Import the consts package in token/jwt/header.go or define the JSONWebTokenHeaderAlgorithm constant within the file.
🔗 Analysis chain

LGTM, but verify JSONWebTokenHeaderAlgorithm definition.

The change simplifies the reference to the algorithm constant by removing the consts package prefix. This is a good practice for improving code readability.

To ensure that JSONWebTokenHeaderAlgorithm is properly defined or imported, please run the following verification script:

This script will help verify that JSONWebTokenHeaderAlgorithm is properly defined or imported, ensuring the change doesn't introduce any undefined variable issues.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the definition or import of JSONWebTokenHeaderAlgorithm

# Test 1: Check if JSONWebTokenHeaderAlgorithm is defined in this file
echo "Checking for JSONWebTokenHeaderAlgorithm definition in header.go:"
rg --type go "^const\s+JSONWebTokenHeaderAlgorithm\s*=" token/jwt/header.go

# Test 2: Check if JSONWebTokenHeaderAlgorithm is imported from another file
echo "Checking for JSONWebTokenHeaderAlgorithm import:"
rg --type go "^import\s+.*JSONWebTokenHeaderAlgorithm" token/jwt/header.go

# Test 3: Search for JSONWebTokenHeaderAlgorithm definition in other files within the token/jwt directory
echo "Searching for JSONWebTokenHeaderAlgorithm definition in other files:"
rg --type go "^const\s+JSONWebTokenHeaderAlgorithm\s*=" token/jwt/

Length of output: 618


Script:

#!/bin/bash
# Description: Comprehensive search for JSONWebTokenHeaderAlgorithm across the entire codebase

# Test 1: Search for any occurrence of JSONWebTokenHeaderAlgorithm
echo "Searching for any occurrences of JSONWebTokenHeaderAlgorithm in the codebase:"
rg "JSONWebTokenHeaderAlgorithm"

# Test 2: Check if JSONWebTokenHeaderAlgorithm is defined as a variable
echo "Checking if JSONWebTokenHeaderAlgorithm is defined as a variable:"
rg --type go "^var\s+JSONWebTokenHeaderAlgorithm\s*="

# Test 3: Search for import statements that might import JSONWebTokenHeaderAlgorithm from external packages
echo "Looking for import statements that include JSONWebTokenHeaderAlgorithm:"
rg --type go "JSONWebTokenHeaderAlgorithm"

Length of output: 2340

Comment on lines 37 to 249
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
claims := MapClaims{}

decoder := jjson.NewDecoder(bytes.NewReader(data))
decoder.SetNumberType(jjson.UnmarshalIntOrFloat)

if err := decoder.Decode(&claims); err != nil {
return errorsx.WithStack(err)
}

var ok bool

for claim, value := range claims {
ok = false

switch claim {
case ClaimJWTID:
c.JTI, ok = value.(string)
case ClaimIssuer:
c.Issuer, ok = value.(string)
case ClaimSubject:
c.Subject, ok = value.(string)
case ClaimAudience:
switch aud := value.(type) {
case string:
ok = true

c.Audience = []string{aud}
case []string:
ok = true

c.Audience = aud
case []any:
ok = true

loop:
for _, av := range aud {
switch a := av.(type) {
case string:
c.Audience = append(c.Audience, a)
default:
ok = false

break loop
}
}
}
case ClaimNonce:
c.Nonce, ok = value.(string)
case ClaimExpirationTime:
c.ExpiresAt, ok = toTime(value, c.ExpiresAt)
case ClaimIssuedAt:
c.IssuedAt, ok = toTime(value, c.IssuedAt)
case ClaimRequestedAt:
c.RequestedAt, ok = toTime(value, c.RequestedAt)
case ClaimAuthenticationTime:
c.AuthTime, ok = toTime(value, c.AuthTime)
case ClaimCodeHash:
c.CodeHash, ok = value.(string)
case ClaimStateHash:
c.StateHash, ok = value.(string)
case ClaimAuthenticationContextClassReference:
c.AuthenticationContextClassReference, ok = value.(string)
default:
if c.Extra == nil {
c.Extra = make(map[string]any)
}

c.Extra[claim] = value

continue
}

if !ok {
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
}
}

return nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring for improved maintainability

The UnmarshalJSON method is quite long and handles multiple responsibilities. Consider refactoring it into smaller, more focused functions to improve readability and maintainability.

Here's a suggested approach:

  1. Create helper functions for handling specific claim types (e.g., handleAudienceClaim, handleTimeClaim).
  2. Use a map of claim handlers to reduce the size of the switch statement.

Example refactoring:

func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
	claims := MapClaims{}
	decoder := jjson.NewDecoder(bytes.NewReader(data))
	decoder.SetNumberType(jjson.UnmarshalIntOrFloat)

	if err := decoder.Decode(&claims); err != nil {
		return errorsx.WithStack(err)
	}

	handlers := map[string]func(any) error{
		consts.ClaimJWTID:      c.handleStringClaim(&c.JTI),
		consts.ClaimIssuer:     c.handleStringClaim(&c.Issuer),
		consts.ClaimSubject:    c.handleStringClaim(&c.Subject),
		consts.ClaimAudience:   c.handleAudienceClaim,
		consts.ClaimNonce:      c.handleStringClaim(&c.Nonce),
		consts.ClaimExpirationTime: c.handleTimeClaim(&c.ExpiresAt),
		// ... other claims ...
	}

	for claim, value := range claims {
		if handler, ok := handlers[claim]; ok {
			if err := handler(value); err != nil {
				return fmt.Errorf("claim %s could not be decoded", claim)
			}
		} else {
			c.handleExtraClaim(claim, value)
		}
	}

	return nil
}

func (c *IDTokenClaims) handleStringClaim(target *string) func(any) error {
	return func(value any) error {
		if s, ok := value.(string); ok {
			*target = s
			return nil
		}
		return fmt.Errorf("expected string value")
	}
}

func (c *IDTokenClaims) handleAudienceClaim(value any) error {
	// Implementation of audience claim handling
}

// ... other helper functions ...

This refactoring improves the code's structure and makes it easier to maintain and extend in the future.

Comment on lines +111 to +245
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix potential exposure of sensitive data in error messages

The error message at line 111 includes the claim value, which could potentially expose sensitive information. This issue was previously flagged in a past review comment.

Apply this diff to address the issue:

-			return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
+			return fmt.Errorf("claim %s could not be decoded", claim)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
}
return fmt.Errorf("claim %s could not be decoded", claim)
}

Comment on lines +259 to +385
[]string{"foo"},
true,
true,
},
{
"ShouldPassMultipleAll",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar"},
},
[]string{"foo", "bar"},
true,
true,
},
{
"ShouldFailNoClaim",
MapClaims{},
[]string{"foo"},
true,
false,
},
{
"ShouldFailNoMatch",
MapClaims{
consts.ClaimAudience: []string{"bar"},
},
[]string{"foo"},
true,
false,
},
{
"ShouldPassNoClaim",
MapClaims{},
[]string{"foo"},
false,
true,
},
{
"ShouldPassTypeAny",
MapClaims{
consts.ClaimAudience: []any{"foo"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassTypeString",
MapClaims{
consts.ClaimAudience: "foo",
},
[]string{"foo"},
true,
true,
},
{
"ShouldFailTypeString",
MapClaims{
consts.ClaimAudience: "bar",
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeNil",
MapClaims{
consts.ClaimAudience: nil,
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeSliceAnyInt",
MapClaims{
consts.ClaimAudience: []any{1, 2, 3},
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeInt",
MapClaims{
consts.ClaimAudience: 1,
},
[]string{"foo"},
true,
false,
},
}
want := true
got := mapClaims.VerifyAudience("foo", false)

if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.VerifyAudienceAny(tc.cmp, tc.required))
})
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving the "ShouldPassMultipleAny" test case

The test cases for TestMapClaims_VerifyAudienceAny are well-structured and cover a wide range of scenarios. However, the "ShouldPassMultipleAny" test case could be improved to better demonstrate the "Any" behavior.

Consider updating the "ShouldPassMultipleAny" test case as follows:

 {
     "ShouldPassMultipleAny",
     MapClaims{
-        consts.ClaimAudience: []string{"foo", "baz"},
+        consts.ClaimAudience: []string{"foo", "baz", "qux"},
     },
-    []string{"bar", "baz"},
+    []string{"bar", "baz", "quux"},
     true,
     true,
 },

This change would better demonstrate that the function passes when any of the audiences match, not just when the first one matches.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestMapClaims_VerifyAudienceAny(t *testing.T) {
testCases := []struct {
name string
have MapClaims
cmp []string
required bool
expected bool
}{
{
"ShouldPass",
MapClaims{
consts.ClaimAudience: []string{"foo"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassMultipleAny",
MapClaims{
consts.ClaimAudience: []string{"foo", "baz"},
},
[]string{"bar", "baz"},
true,
true,
},
{
"ShouldPassMultiple",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassMultipleAll",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar"},
},
[]string{"foo", "bar"},
true,
true,
},
{
"ShouldFailNoClaim",
MapClaims{},
[]string{"foo"},
true,
false,
},
{
"ShouldFailNoMatch",
MapClaims{
consts.ClaimAudience: []string{"bar"},
},
[]string{"foo"},
true,
false,
},
{
"ShouldPassNoClaim",
MapClaims{},
[]string{"foo"},
false,
true,
},
{
"ShouldPassTypeAny",
MapClaims{
consts.ClaimAudience: []any{"foo"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassTypeString",
MapClaims{
consts.ClaimAudience: "foo",
},
[]string{"foo"},
true,
true,
},
{
"ShouldFailTypeString",
MapClaims{
consts.ClaimAudience: "bar",
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeNil",
MapClaims{
consts.ClaimAudience: nil,
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeSliceAnyInt",
MapClaims{
consts.ClaimAudience: []any{1, 2, 3},
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeInt",
MapClaims{
consts.ClaimAudience: 1,
},
[]string{"foo"},
true,
false,
},
}
want := true
got := mapClaims.VerifyAudience("foo", false)
if want != got {
t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.VerifyAudienceAny(tc.cmp, tc.required))
})
func TestMapClaims_VerifyAudienceAny(t *testing.T) {
testCases := []struct {
name string
have MapClaims
cmp []string
required bool
expected bool
}{
{
"ShouldPass",
MapClaims{
consts.ClaimAudience: []string{"foo"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassMultipleAny",
MapClaims{
consts.ClaimAudience: []string{"foo", "baz", "qux"},
},
[]string{"bar", "baz", "quux"},
true,
true,
},
{
"ShouldPassMultiple",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassMultipleAll",
MapClaims{
consts.ClaimAudience: []string{"foo", "bar"},
},
[]string{"foo", "bar"},
true,
true,
},
{
"ShouldFailNoClaim",
MapClaims{},
[]string{"foo"},
true,
false,
},
{
"ShouldFailNoMatch",
MapClaims{
consts.ClaimAudience: []string{"bar"},
},
[]string{"foo"},
true,
false,
},
{
"ShouldPassNoClaim",
MapClaims{},
[]string{"foo"},
false,
true,
},
{
"ShouldPassTypeAny",
MapClaims{
consts.ClaimAudience: []any{"foo"},
},
[]string{"foo"},
true,
true,
},
{
"ShouldPassTypeString",
MapClaims{
consts.ClaimAudience: "foo",
},
[]string{"foo"},
true,
true,
},
{
"ShouldFailTypeString",
MapClaims{
consts.ClaimAudience: "bar",
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeNil",
MapClaims{
consts.ClaimAudience: nil,
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeSliceAnyInt",
MapClaims{
consts.ClaimAudience: []any{1, 2, 3},
},
[]string{"foo"},
true,
false,
},
{
"ShouldFailTypeInt",
MapClaims{
consts.ClaimAudience: 1,
},
[]string{"foo"},
true,
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, tc.have.VerifyAudienceAny(tc.cmp, tc.required))
})

Comment on lines +21 to +289
encAlg: string(jose.PBES2_HS256_A128KW),
enc: string(jose.A256GCM),
csigned: true,
secret: key128,
jwks: issuerJWKSenc,
jwksURI: "",
}

strategy := &DefaultStrategy{
Config: config,
Issuer: issuer,
}

claims := MapClaims{
"value": 1,
}

headers1 := &Headers{
Extra: map[string]any{
JSONWebTokenHeaderType: JSONWebTokenTypeAccessToken,
},
}

var headersEnc *Headers

var (
token1, signature1 string
)

token1, signature1, err = strategy.Encode(ctx, claims, WithHeaders(headers1), WithClient(client))
require.NoError(t, err)
assert.NotEmpty(t, signature1)

require.True(t, IsSignedJWT(token1))

headersEnc = &Headers{}

var (
token2, signature2 string
)

headers2 := &Headers{
Extra: map[string]any{
JSONWebTokenHeaderType: JSONWebTokenTypeJWT,
},
}

token2, signature2, err = strategy.Encode(ctx, claims, WithHeaders(headers2), WithHeadersJWE(headersEnc), WithClient(clientEnc))
require.NoError(t, err)
require.True(t, IsEncryptedJWT(token2))
require.NotEmpty(t, signature2)

var (
token3, signature3 string
)

token3, signature3, err = strategy.Encode(ctx, claims, WithHeaders(headers1), WithHeadersJWE(headersEnc), WithClient(clientEncAsymmetric))
require.NoError(t, err)
assert.NotEmpty(t, signature3)

clientIssuer := &DefaultIssuer{
jwks: clientIssuerJWKS,
}

clientStrategy := &DefaultStrategy{
Config: config,
Issuer: clientIssuer,
}

issuerClient := &testClient{
kid: "es512-sig",
alg: "ES512",
encKID: "",
encAlg: "",
enc: "",
csigned: true,
jwks: issuerClientJWKS,
jwksURI: "",
}

tokenString, signature, jwe, err := clientStrategy.Decrypt(ctx, token2, WithClient(clientEncAsymmetric))
require.NoError(t, err)
assert.NotEmpty(t, signature)
assert.NotEmpty(t, tokenString)
assert.NotNil(t, jwe)

tokenString, signature, jwe, err = clientStrategy.Decrypt(ctx, token3, WithClient(clientEncAsymmetric))
require.NoError(t, err)

tok, err := clientStrategy.Decode(ctx, token1, WithClient(issuerClient))
require.NoError(t, err)
require.NotNil(t, tok)

tok, err = clientStrategy.Decode(ctx, token2, WithClient(issuerClient))
require.NoError(t, err)

tok, err = clientStrategy.Decode(ctx, token3, WithClient(clientEncAsymmetric))
require.NoError(t, err)
require.NotNil(t, tok)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider breaking down TestDefaultStrategy into smaller, focused test cases.

While this test function is comprehensive, its length and complexity make it difficult to understand and maintain. Consider refactoring it into smaller, more focused test cases. This would improve readability and make it easier to identify the purpose of each test.

For example, you could create separate test functions for:

  1. JWT encoding
  2. JWT decoding
  3. Encrypted JWT handling
  4. Different key types (RSA, ECDSA)

This refactoring will make the tests more manageable and easier to update in the future.

case string:
aud = append(aud, v)
case []string:
aud = ClaimStrings(v)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unreachable type assertion case []string

In the UnmarshalJSON method for ClaimStrings, the case []string: might never be executed because JSON unmarshalling into an interface{} will not produce a []string directly. It will produce a []interface{} instead. Consider removing this case or adjusting the unmarshalling logic.

Apply this diff to remove the unreachable case:

 switch v := value.(type) {
 case string:
     aud = append(aud, v)
-case []string:
-    aud = ClaimStrings(v)
 case []interface{}:
     for _, vv := range v {

Committable suggestion was skipped due to low confidence.

Comment on lines +10 to +14
SigningMethodNone = jose.SignatureAlgorithm(JSONWebTokenAlgNone)

// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens.
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure security implications of allowing 'none' signature algorithm are addressed.

Including SigningMethodNone and UnsafeAllowNoneSignatureType enables the use of the 'none' signature algorithm, which effectively disables signature verification. This poses significant security risks. Please ensure this is intentional and that appropriate safeguards are in place to prevent misuse in production environments.

Comment on lines +12 to +13
// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens.
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix grammatical error in comment.

The comment in line 12 contains a grammatical error. It should say "should be used" instead of "should be use".

Apply this diff to correct the comment:

-// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens.
+// UnsafeAllowNoneSignatureType is unsafe to use and should be used to correctly sign and verify alg:none JWT tokens.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// UnsafeAllowNoneSignatureType is unsafe to use and should be use to correctly sign and verify alg:none JWT tokens.
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"
// UnsafeAllowNoneSignatureType is unsafe to use and should be used to correctly sign and verify alg:none JWT tokens.
UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"

// SignatureAlgorithms contain all algorithms excluding 'none'.
SignatureAlgorithms = []jose.SignatureAlgorithm{jose.HS256, jose.HS384, jose.HS512, jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512, jose.ES256, jose.ES384, jose.ES512}

// EncryptionKeyAlgorithms contains all valid JWE's for OAuth 2.0 and OpenID Connect 1.0.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct pluralization in comment for accuracy.

The comment mentions "all valid JWE's". The plural form should be "JWEs" without an apostrophe.

Apply this diff to correct the comment:

-	// EncryptionKeyAlgorithms contains all valid JWE's for OAuth 2.0 and OpenID Connect 1.0.
+	// EncryptionKeyAlgorithms contains all valid JWEs for OAuth 2.0 and OpenID Connect 1.0.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// EncryptionKeyAlgorithms contains all valid JWE's for OAuth 2.0 and OpenID Connect 1.0.
// EncryptionKeyAlgorithms contains all valid JWEs for OAuth 2.0 and OpenID Connect 1.0.

Comment on lines +117 to +123
if err = claims.Valid(); err != nil {
if e, ok := err.(*ValidationError); !ok {
err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
}

return token, err
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect Error Wrapping in Claims Validation

There is a potential issue in the error handling when validating claims. In the following code:

if err = claims.Valid(); err != nil {
    if e, ok := err.(*ValidationError); !ok {
        err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
    }
    return token, err
}

If the type assertion e, ok := err.(*ValidationError) fails (i.e., ok is false), e will be nil. However, you're attempting to use e (which is nil) as the Inner error in a new ValidationError, potentially losing the original error information stored in err.

Consider modifying the code to properly wrap the original error err when it's not a *ValidationError:

if err = claims.Valid(); err != nil {
-   if e, ok := err.(*ValidationError); !ok {
-       err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
+   if _, ok := err.(*ValidationError); !ok {
+       err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
    }
    return token, err
}

This change ensures that the original err is preserved and properly wrapped.

Copy link

@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: 14

🧹 Outside diff range and nitpick comments (17)
rfc8628_device_authorize_write_test.go (1)

Line range hint 1-58: Summary of changes and recommendations

The modifications in this file focus on standardizing time precision for JWT operations by using Truncate(jwt.TimePrecision). This change improves consistency in time handling across the codebase.

However, there are a couple of points to address:

  1. Verify that the change from Round to Truncate doesn't introduce any unexpected behavior in the token polling interval or code lifespan calculations.
  2. Check and correct the potential typo where Second() is used instead of Seconds() in the expiration time calculation.

After addressing these points, consider adding comments to explain the rationale behind using Truncate(jwt.TimePrecision) for better code maintainability.

handler/openid/flow_refresh_token.go (1)

Line range hint 1-110: Overall improvement in JWT handling with a note on ExpirationTime

The changes in this file generally improve JWT handling in the refresh token flow by aligning with JWT standards and best practices. The use of jwt.Now() for IssuedAt is particularly good.

However, there's a potential issue with setting ExpirationTime to an empty time, which needs to be addressed to ensure proper token expiration handling.

Consider implementing a consistent method for setting token expiration times across the codebase. This could involve:

  1. Creating a helper function that calculates the appropriate expiration time based on the token type and configuration.
  2. Ensuring that all token creation and refresh operations use this helper function to set expiration times.

This approach would improve consistency and reduce the risk of issues related to token expiration.

token/jwt/claims_jarm.go (1)

10-21: LGTM! Consider adding validation for the lifespan parameter.

The NewJARMClaims function is well-implemented and provides a convenient way to create a new JARMClaims instance with sensible defaults. The use of uuid.NewString() for JTI ensures uniqueness, which is good.

Consider adding a check to ensure that the lifespan parameter is positive. This would prevent the creation of claims with an expiration time in the past:

if lifespan <= 0 {
    return nil, fmt.Errorf("lifespan must be positive")
}
token/jwt/claims_jwt.go (2)

241-268: LGTM with suggestion: Robust toInt64 function

The toInt64 function provides comprehensive type checking and conversion, handling various numeric types and json.Number. The error handling is thorough, especially for json.Number conversion.

Consider adding a check for potential precision loss when converting float64 to int64. For large float values, this conversion might lead to unexpected results. You could add a warning or error for float values that exceed math.MaxInt64 or are less than math.MinInt64.

Example:

case float64:
    if t > math.MaxInt64 || t < math.MinInt64 {
        return 0, false // or handle this case as appropriate
    }
    return int64(t), true

270-302: LGTM with suggestion: Well-implemented toNumericDate function

The toNumericDate function provides comprehensive type checking and conversion for various numeric types and json.Number. It properly handles the case where the input is 0, returning nil.

Consider adding a check for negative values, as they might not be valid for dates. You could either return an error or handle them in a way that's consistent with your application's requirements.

Example:

func toNumericDate(v any) (date *NumericDate, err error) {
    // ... existing code ...

    // Add this check after converting to float64
    if vv < 0 {
        return nil, newError("negative values are not allowed for dates", ErrInvalidValue)
    }

    return newNumericDateFromSeconds(vv), nil
}
README.md (2)

107-107: Minor style suggestion: Consider adding a period after "etc"

In American English, it's common to add a period after abbreviations like "etc.". However, given the technical nature of this document and the consistency with other bullet points, this change is optional and up to your preference.

If you choose to make this change, here's the suggested modification:

- - [x] Testing Package (mocks, etc)
+ - [x] Testing Package (mocks, etc.)
🧰 Tools
🪛 LanguageTool

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)


Documentation Updates Missing

While some test cases related to the fixes have been implemented, there are no additional documentation updates beyond the existing README.md. Please ensure that all changes are thoroughly documented to maintain clarity and completeness.

🔗 Analysis chain

Line range hint 30-52: Comprehensive fixes implemented across various flows

Great job on addressing multiple issues across different flows and functionalities. The inclusion of commit references for each fix is helpful for tracking changes.

To ensure the effectiveness of these fixes:

  1. Consider adding or updating relevant unit tests for each fix.
  2. Perform integration testing to verify that the fixes work correctly in the context of the entire system.
  3. Update any related documentation or API references to reflect these changes.

To verify the implementation of these fixes, you can run the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of fixes by checking for relevant test files and documentation updates.

# Check for test files related to the fixes
echo "Checking for relevant test files:"
rg --type go -l "(TestRefreshFlow|TestPKCEFlow|TestOpenIDFlow|TestWriteRevocationResponse)"

# Check for documentation updates
echo "Checking for documentation updates:"
rg --type markdown -l "(Refresh Flow|PKCE Flow|OpenID Flow|Revocation Response)"

Length of output: 3041


Script:

#!/bin/bash
# Description: Verify the presence of test cases and documentation updates for the implemented fixes.

# Search for any test files that include the fix descriptions
echo "Searching for test cases related to the fixes:"
fd -t f -e go . | xargs rg -l "(TestRefreshFlow|TestPKCEFlow|TestOpenIDFlow|TestWriteRevocationResponse)"

# Search for documentation files that mention the fixes
echo "Searching for documentation updates related to the fixes:"
rg -l "(Refresh Flow|PKCE Flow|OpenID Flow|Revocation Response)" --type md

Length of output: 481

token/jwt/claims_map.go (2)

43-64: LGTM: GetSubject and VerifySubject functions are well-implemented

Both GetSubject and VerifySubject functions are correctly implemented and consistent with their 'iss' counterparts. They provide the necessary functionality for handling the 'sub' claim.

Consider refactoring the VerifyIssuer and VerifySubject functions to reduce code duplication. You could create a generic verification function that takes the claim key and comparison function as parameters. For example:

func (m MapClaims) verifyStringClaim(claimKey string, cmp string, required bool, getClaim func() (string, error)) bool {
    claim, err := getClaim()
    if err != nil {
        return false
    }
    if claim == "" {
        return !required
    }
    return validString(claim, cmp, required)
}

// Then use it like this:
func (m MapClaims) VerifyIssuer(cmp string, required bool) bool {
    return m.verifyStringClaim(ClaimIssuer, cmp, required, m.GetIssuer)
}

func (m MapClaims) VerifySubject(cmp string, required bool) bool {
    return m.verifyStringClaim(ClaimSubject, cmp, required, m.GetSubject)
}

This approach would make the code more DRY and easier to maintain.


217-282: LGTM: Comprehensive Valid function with minor improvement suggestion

The new Valid function provides a thorough and flexible mechanism for validating JWT claims. It correctly utilizes individual verification functions and accumulates errors using the ValidationError struct.

Consider improving the error messages to be more specific. Instead of generic messages like "Token is expired", you could include more details. For example:

if !m.VerifyExpirationTime(now, vopts.expRequired) {
    expTime, _ := m.GetExpirationTime()
    vErr.Inner = fmt.Errorf("Token is expired. Expiration time: %v, Current time: %v", expTime, now)
    vErr.Errors |= ValidationErrorExpired
}

This would provide more context in the error messages, making debugging easier for users of this library.

handler/openid/strategy_jwt.go (1)

171-171: LGTM: Improved prompt validation with better error handling

The updates to the prompt validation logic are excellent improvements:

  1. The code now handles potential nil values in claims.AuthTime, preventing nil pointer dereferences.
  2. Error messages have been enhanced to provide more detailed information about validation failures.
  3. The use of GetAuthTimeSafe() and GetRequestedAtSafe() methods ensures safer time comparisons.

These changes significantly improve the robustness and debuggability of the prompt validation process.

Consider extracting the error messages into constants to improve maintainability and consistency. For example:

const (
    ErrPromptNoneReauthentication = "Failed to generate id token because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed."
    ErrPromptLoginNoReauthentication = "Failed to generate id token because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden."
)

Then use these constants in the error messages:

WithDebugf(ErrPromptNoneReauthentication, claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe())

Also applies to: 178-180, 183-185

integration/oidc_explicit_test.go (1)

161-162: Consistent JWT claim updates and refactoring opportunity

The changes here continue the pattern of updating RequestedAt and AuthTime fields consistently across test cases. While this consistency is commendable, the repetition of these changes across multiple test cases suggests a potential opportunity for refactoring.

Consider creating a helper function to generate the IDTokenClaims with the updated time fields. This could reduce duplication and make future updates easier to manage.

Here's a suggested helper function:

func newTestIDTokenClaims(subject string) *jwt.IDTokenClaims {
	return &jwt.IDTokenClaims{
		Subject:     subject,
		RequestedAt: jwt.Now(),
		AuthTime:    jwt.NewNumericDate(time.Now().Add(time.Second)),
	}
}

You could then use this helper function in your test cases:

session: newIDSession(newTestIDTokenClaims("peter")),
handler/oauth2/flow_authorize_code_token.go (2)

114-114: LGTM. Consider refactoring for DRY principle.

The change to use Truncate(jwt.TimePrecision) for the refresh token expiration time is consistent with the access token change and improves overall precision consistency.

To reduce code duplication and improve maintainability, consider extracting the time calculation into a helper function. Here's a suggested refactoring:

+func calculateExpirationTime(duration time.Duration) time.Time {
+    return time.Now().UTC().Add(duration).Truncate(jwt.TimePrecision)
+}

 // In HandleTokenEndpointRequest method
-request.GetSession().SetExpiresAt(oauth2.AccessToken, time.Now().UTC().Add(atLifespan).Truncate(jwt.TimePrecision))
+request.GetSession().SetExpiresAt(oauth2.AccessToken, calculateExpirationTime(atLifespan))

 if rtLifespan > -1 {
-    request.GetSession().SetExpiresAt(oauth2.RefreshToken, time.Now().UTC().Add(rtLifespan).Truncate(jwt.TimePrecision))
+    request.GetSession().SetExpiresAt(oauth2.RefreshToken, calculateExpirationTime(rtLifespan))
 }

This refactoring will make the code more maintainable and reduce the risk of inconsistencies in future modifications.


Line range hint 1-214: Summary: Time precision improvements enhance token handling consistency.

The changes in this file improve the consistency of time precision handling for both access and refresh tokens. This is a positive change that aligns with best practices for JWT handling.

To ensure the changes don't have any unintended consequences:

  1. Verify that all token validation logic (both client-side and server-side) is compatible with this new precision.
  2. Update any documentation or API specifications to reflect the precise time handling for token expiration.
  3. Consider adding a test case that specifically checks for the correct precision of token expiration times.

These steps will help maintain the integrity of the authentication system while benefiting from the improved precision.

token/jwt/claims_id_token.go (1)

38-60: LGTM: Well-implemented getter methods for claims

The new getter methods for claims are implemented consistently and adhere to the Claims interface. They provide a clean way to access claim data.

However, there's a minor inconsistency in the GetNotBefore method:

Consider updating the GetNotBefore method to match the pattern of other getters:

func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
	return nil, nil // or implement actual logic if needed
}

This change would make the method consistent with others and avoid relying on an unseen toNumericDate function.

handler/oauth2/flow_refresh.go (2)

154-154: Approve change with refactoring suggestion.

The change from Round(time.Second) to Truncate(jwt.TimePrecision) for setting the refresh token expiration time is consistent with the previous change for the access token. This ensures uniform time precision handling across different token types.

To improve code maintainability and reduce duplication, consider refactoring the time adjustment logic into a separate function. This would centralize the logic and make future changes easier. Here's a suggested implementation:

func adjustTokenExpiration(t time.Time) time.Time {
    return t.Truncate(jwt.TimePrecision)
}

Then, you can use this function for both access and refresh token expiration times:

request.GetSession().SetExpiresAt(oauth2.AccessToken, adjustTokenExpiration(time.Now().UTC().Add(atLifespan)))
// ...
request.GetSession().SetExpiresAt(oauth2.RefreshToken, adjustTokenExpiration(time.Now().UTC().Add(rtLifespan)))

This refactoring would make the code more DRY (Don't Repeat Yourself) and easier to maintain if the time adjustment logic needs to change in the future.


Line range hint 1-300: Summary of changes in flow_refresh.go

The changes in this file improve the precision of token expiration times by using Truncate(jwt.TimePrecision) instead of Round(time.Second) for both access and refresh tokens. These changes are consistent and align with best practices for JWT handling.

To further improve the code:

  1. Verify the impact of using jwt.TimePrecision on dependent systems.
  2. Consider refactoring the time adjustment logic into a separate function to reduce code duplication and improve maintainability.
  3. Ensure that the value of jwt.TimePrecision is appropriate for your use case and consistent across the entire authentication system.
token/jwt/validate.go (1)

21-67: Add documentation comments for exported functions

The exported functions in this file lack documentation comments. Adding comments to exported functions improves code clarity and helps users understand their purpose and usage.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1438817 and a020d9b.

📒 Files selected for processing (41)
  • README.md (3 hunks)
  • handler/oauth2/flow_authorize_code_token.go (2 hunks)
  • handler/oauth2/flow_authorize_code_token_test.go (2 hunks)
  • handler/oauth2/flow_authorize_implicit.go (2 hunks)
  • handler/oauth2/flow_generic_code_token.go (2 hunks)
  • handler/oauth2/flow_refresh.go (2 hunks)
  • handler/oauth2/flow_refresh_test.go (11 hunks)
  • handler/oauth2/flow_resource_owner.go (2 hunks)
  • handler/oauth2/flow_resource_owner_test.go (2 hunks)
  • handler/oauth2/strategy_jwt_profile_test.go (8 hunks)
  • handler/openid/flow_device_authorization_test.go (4 hunks)
  • handler/openid/flow_hybrid.go (2 hunks)
  • handler/openid/flow_hybrid_test.go (4 hunks)
  • handler/openid/flow_refresh_token.go (3 hunks)
  • handler/openid/strategy_jwt.go (6 hunks)
  • handler/openid/strategy_jwt_test.go (13 hunks)
  • handler/openid/validator.go (3 hunks)
  • handler/openid/validator_test.go (15 hunks)
  • handler/rfc8628/device_authorize_handler.go (2 hunks)
  • handler/rfc8628/token_endpoint_handler_test.go (2 hunks)
  • handler/rfc8628/user_authorize_handler_test.go (5 hunks)
  • handler/rfc8693/access_token_type_handler.go (2 hunks)
  • handler/rfc8693/custom_jwt_type_handler.go (5 hunks)
  • handler/rfc8693/refresh_token_type_handler.go (2 hunks)
  • integration/oidc_explicit_test.go (6 hunks)
  • internal/test_helpers.go (2 hunks)
  • introspection_response_writer_test.go (1 hunks)
  • rfc8628_device_authorize_write_test.go (2 hunks)
  • token/jarm/generate.go (2 hunks)
  • token/jwt/claims_id_token.go (7 hunks)
  • token/jwt/claims_id_token_test.go (3 hunks)
  • token/jwt/claims_jarm.go (2 hunks)
  • token/jwt/claims_jarm_test.go (2 hunks)
  • token/jwt/claims_jwt.go (5 hunks)
  • token/jwt/claims_jwt_test.go (5 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/claims_test.go (1 hunks)
  • token/jwt/date.go (1 hunks)
  • token/jwt/token.go (3 hunks)
  • token/jwt/token_test.go (8 hunks)
  • token/jwt/validate.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • introspection_response_writer_test.go
🚧 Files skipped from review as they are similar to previous changes (17)
  • handler/oauth2/strategy_jwt_profile_test.go
  • handler/openid/flow_device_authorization_test.go
  • handler/openid/flow_hybrid.go
  • handler/openid/flow_hybrid_test.go
  • handler/openid/strategy_jwt_test.go
  • handler/openid/validator.go
  • handler/openid/validator_test.go
  • handler/rfc8628/token_endpoint_handler_test.go
  • handler/rfc8693/custom_jwt_type_handler.go
  • internal/test_helpers.go
  • token/jarm/generate.go
  • token/jwt/claims_id_token_test.go
  • token/jwt/claims_jarm_test.go
  • token/jwt/claims_jwt_test.go
  • token/jwt/claims_test.go
  • token/jwt/date.go
  • token/jwt/token_test.go
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)

🔇 Additional comments (74)
handler/rfc8628/device_authorize_handler.go (2)

4-4: LGTM: Import statement addition is appropriate.

The addition of the jwt package import is necessary and consistent with the changes made in the expiration time calculation.


Line range hint 1-78: Summary: Improved consistency in JWT time precision handling.

The changes made to this file enhance the consistency of time precision handling in JWT operations. The modification is minimal but significant, affecting only the expiration time calculation for device and user codes. The overall logic and structure of the HandleRFC8628DeviceAuthorizeEndpointRequest function remain intact, with the change focused on aligning the time precision with the JWT package's standards.

These changes should lead to more consistent behavior in token expiration across the application. However, it's crucial to ensure that this new approach is applied consistently throughout the codebase to maintain uniformity in JWT handling.

handler/oauth2/flow_authorize_implicit.go (1)

7-7: Verify the impact of changing token expiration time calculation

The modification in how the access token expiration time is calculated could have significant implications:

  1. The change from Round(time.Second) to Truncate(jwt.TimePrecision) may alter the exact expiration time of access tokens.
  2. It introduces a dependency on the jwt package for time precision.

While this change likely aims to standardize time precision across the application, ensuring consistency in token validation, there are a few points to consider:

  1. The value of jwt.TimePrecision is not immediately clear. Could you please clarify what this value is set to and why it was chosen?
  2. This change might affect existing systems that depend on the previous rounding behavior. Have you considered the potential impact on token validation in other parts of the system or in client applications?
  3. It would be helpful to add a comment explaining the rationale behind this change, especially if it's part of a larger effort to standardize time handling in JWT operations.

To ensure this change doesn't introduce unexpected behavior, please run the following verification:

This will help us understand the consistency of TimePrecision usage across the codebase.

Also applies to: 74-74

✅ Verification successful

Verified the impact of changing token expiration time calculation

  • jwt.TimePrecision is defined as time.Second.
  • All usages of Truncate(jwt.TimePrecision) are consistent with this definition.
  • The change standardizes time precision across the application, ensuring consistency in token validation.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the definition and usage of jwt.TimePrecision

# Test 1: Find the definition of TimePrecision
echo "TimePrecision definition:"
rg --type go "TimePrecision.*=.*" -A 3

# Test 2: Check for other usages of TimePrecision in the codebase
echo "\nOther usages of TimePrecision:"
rg --type go "Truncate\(.*TimePrecision\)" -A 3

Length of output: 13263

handler/openid/flow_refresh_token.go (1)

96-96: Approved: Improved JWT timestamp handling

The change from time.Now().Truncate(time.Second) to jwt.Now() for setting the IssuedAt claim is a good improvement. It ensures consistency with the JWT library's time handling and aligns with best practices for JWT timestamp management.

token/jwt/claims_jarm.go (7)

25-31: LGTM! Good use of pointers and omitempty tag.

The changes to the JARMClaims struct are well-thought-out:

  • Using pointers for IssuedAt and ExpirationTime allows for nil checks and better represents optional fields.
  • The addition of the omitempty tag ensures that these fields are omitted from JSON output when they are nil, which is more efficient and cleaner.

These changes improve the flexibility and JSON representation of the struct.


83-92: LGTM! Good handling of nil values and consistent date representation.

The updates to the ToMap method are well-implemented:

  • The nil checks for IssuedAt and ExpirationTime are necessary and correctly implemented due to the change to pointer types.
  • Using the Unix() method to convert NumericDate to integer ensures a consistent representation of dates.

These changes improve the robustness of the method and ensure correct handling of optional date fields.


98-101: LGTM! Simple and effective implementation.

The ToMapClaims method is a straightforward and effective wrapper around ToMap. It provides a convenient way to convert JARMClaims to MapClaims, which enhances interoperability with other components that expect MapClaims.


130-133: LGTM! Simple and effective implementation.

The FromMapClaims method is a straightforward and effective wrapper around FromMap. It provides a convenient way to populate JARMClaims from MapClaims, which enhances interoperability with other components that use MapClaims.


149-160: LGTM! Good handling of optional date fields.

The toNumericDate method is well-implemented:

  • It correctly handles the case where the key is not found in Extra by returning nil.
  • The error handling is appropriate, allowing the caller to decide how to handle conversion errors.

This method provides a convenient and robust way to handle optional date fields in the claims.


162-176: LGTM! Good error handling and type checking.

The toString method is well-implemented:

  • It correctly handles the case where the key is not found in Extra by returning an empty string.
  • The type assertion and error handling are appropriate, providing informative errors when the value is not a string.

This method provides a robust way to handle optional string fields in the claims, with good error reporting.


179-181: LGTM! Good use of compile-time interface check.

The type assertion _ Claims = (*JARMClaims)(nil) is a great practice. It ensures at compile-time that JARMClaims implements the Claims interface, which helps catch any interface implementation errors early in the development process.

handler/oauth2/flow_resource_owner.go (1)

74-74: Verify impact of precision change on access token expiration.

The modification to use Truncate(jwt.TimePrecision) instead of the previous rounding method aligns the access token expiration time with a specific precision defined in the JWT package. This change could potentially affect token validation and consistency across the system.

Please run the following script to check for any discrepancies in token validation logic:

Consider updating any relevant documentation or API specifications to reflect this change in token expiration precision. Also, ensure that all components dealing with token expiration are using the same precision to maintain consistency.

handler/rfc8693/refresh_token_type_handler.go (1)

4-4: LGTM: Import statement added for jwt package

The addition of the import statement for the jwt package is necessary and consistent with the changes made in the issue method.

token/jwt/claims_jwt.go (3)

104-142: LGTM: Improved constant usage in ToMap method

The refactoring to use direct claim constants (e.g., ClaimSubject, ClaimIssuer) instead of accessing them through the consts package improves code readability and reduces package dependencies. The logic remains unchanged, ensuring the method's functionality is preserved.


229-239: LGTM: Well-implemented toTime function

The new toTime function provides a robust way to convert various input types to a time.Time value. It properly handles conversion failures and ensures consistency by using UTC. Good job on improving the time-related claim processing.


Line range hint 1-302: Overall assessment: Significant improvements in JWT claim handling

The changes in this file substantially enhance the robustness and flexibility of JWT claim handling. The introduction of new utility functions (toTime, toInt64, toNumericDate) provides better support for various data types and improves type safety. The refactoring in the ToMap method improves code readability.

While the changes are generally very good, there are a few areas where additional checks could be implemented to handle edge cases (e.g., precision loss in float to int conversion, negative date values). Additionally, as noted in a previous review comment, the FromMap method could benefit from handling the success status returned by toTime.

These enhancements significantly improve the overall quality and reliability of the JWT claim processing in your application.

handler/rfc8693/access_token_type_handler.go (1)

165-165: Approve the change in refresh token expiration time calculation.

The modification from Round(time.Second) to Truncate(jwt.TimePrecision) improves consistency with JWT standards and likely aligns with other parts of the codebase. This change standardizes the timestamp precision for JWT tokens.

To ensure the change doesn't unexpectedly affect token lifetimes, please verify the value of jwt.TimePrecision:

If jwt.TimePrecision is less than 1 second, consider adding a comment explaining the rationale for the increased precision.

✅ Verification successful

Verified that jwt.TimePrecision is set to time.Second, ensuring the change to use Truncate(jwt.TimePrecision) correctly standardizes timestamp precision without affecting token lifespans.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the value of jwt.TimePrecision

# Test: Search for the definition of jwt.TimePrecision
rg --type go -g '!*_test.go' 'const\s+TimePrecision\s*=' 

Length of output: 58


Script:

#!/bin/bash
# Description: Search for all occurrences of TimePrecision in Go files excluding test files

rg --type go -g '!*_test.go' 'TimePrecision'

Length of output: 2903

README.md (4)

25-27: LGTM: JWT and JWKS logic improvements completed

The completion of simplifying internal JWT logic and implementing internal JWKS logic aligns well with the project's goals. These improvements should enhance the overall JWT management system.


106-112: Significant feature enhancements implemented

Excellent progress on implementing various features, particularly:

  • JWE support for Client Authentication and Issuance
  • Key Management
  • Support for s_hash

These additions greatly enhance the library's capabilities and security features.

To ensure these new features are well-documented and easily adoptable:

  1. Update the user documentation to include usage examples for the new features, especially JWE support and Key Management.
  2. Consider creating or updating API reference documentation for any new public interfaces related to these features.
  3. If not already done, add integration tests that demonstrate the correct usage and behavior of these new features in real-world scenarios.

To verify the implementation and documentation of these features, you can run the following script:

#!/bin/bash
# Description: Verify the implementation and documentation of new features

# Check for implementation files
echo "Checking for implementation files:"
rg --type go -l "(JWE.*Client Authentication|JWE.*Issuance|Key Management|s_hash)"

# Check for test files
echo "Checking for test files:"
rg --type go -l "Test.*(JWE|KeyManagement|SHash)"

# Check for documentation updates
echo "Checking for documentation updates:"
rg --type markdown -l "(JWE|\"Key Management\"|s_hash)"
🧰 Tools
🪛 LanguageTool

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)


128-128: Verification needed for JWT dependency removal

The removal of the github.com/golang-jwt/jwt dependency is noted. This change aligns with the earlier simplification of internal JWT logic.

To ensure this removal doesn't negatively impact the project:

  1. Verify that all functionality previously provided by this dependency has been adequately replaced or is no longer needed.
  2. Update any documentation or code comments that may have referenced this dependency.
  3. Run a full test suite to ensure no regressions have been introduced by this removal.

To verify the impact of this dependency removal, you can run the following script:

#!/bin/bash
# Description: Verify the removal of the github.com/golang-jwt/jwt dependency

# Check for any remaining imports of the removed dependency
echo "Checking for any remaining imports of github.com/golang-jwt/jwt:"
rg --type go "github.com/golang-jwt/jwt"

# Check for any mentions in documentation
echo "Checking for any mentions in documentation:"
rg --type markdown "github.com/golang-jwt/jwt"

# Check go.mod for the dependency
echo "Checking go.mod for the dependency:"
grep "github.com/golang-jwt/jwt" go.mod

129-131: Dependency migrations require thorough testing

The migration of dependencies to newer versions is a positive step for maintaining the project's security and leveraging new features. Specifically:

  • github.com/go-jose/go-jose/v3 to github.com/go-jose/go-jose/v4
  • github.com/golang/mock to github.com/uber-go/mock
  • github.com/cristalhq/jwt/v4 to github.com/golang-jwt/jwt/v5

To ensure these migrations don't introduce any issues:

  1. Review the changelogs of each updated dependency to understand any breaking changes or new features.
  2. Update any code that may be affected by these changes, particularly focusing on areas that use these dependencies directly.
  3. Run a comprehensive test suite, including integration tests, to catch any potential regressions or compatibility issues.
  4. Update any documentation that references these dependencies or their usage.

To verify the impact of these dependency migrations, you can run the following script:

#!/bin/bash
# Description: Verify the migration of dependencies

# Check for updated imports
echo "Checking for updated imports:"
rg --type go "github.com/go-jose/go-jose/v4"
rg --type go "github.com/uber-go/mock"
rg --type go "github.com/golang-jwt/jwt/v5"

# Check go.mod for the updated dependencies
echo "Checking go.mod for updated dependencies:"
grep -E "github.com/go-jose/go-jose/v4|github.com/uber-go/mock|github.com/golang-jwt/jwt/v5" go.mod

# Check for any remaining old imports
echo "Checking for any remaining old imports:"
rg --type go "github.com/go-jose/go-jose/v3"
rg --type go "github.com/golang/mock"
rg --type go "github.com/cristalhq/jwt/v4"
handler/oauth2/flow_resource_owner_test.go (2)

7-7: LGTM: New import for JWT package.

The addition of the jwt package import is appropriate and necessary for the changes made in the test assertions.


93-94: Improved time precision in test assertions.

The change from Round(time.Second) to Truncate(jwt.TimePrecision) in the time assertions is a good improvement. This modification ensures that the test comparisons are consistent with the actual JWT token generation precision, leading to more accurate and reliable test results.

token/jwt/claims_map.go (7)

19-21: LGTM: New GetIssuer function looks good

The GetIssuer function is a well-implemented addition for retrieving the 'iss' claim. It correctly uses the toString method to handle the conversion.


25-40: LGTM: VerifyIssuer function is well-implemented

The VerifyIssuer function provides a robust mechanism for verifying the 'iss' claim. It correctly handles cases where the claim is unset and not required, and properly uses the GetIssuer function.


67-127: LGTM: Comprehensive audience verification functions

The additions and modifications to the audience-related functions (GetAudience, VerifyAudience, VerifyAudienceAll, VerifyAudienceAny) provide a robust and flexible system for handling the 'aud' claim. The implementation is consistent across all functions and makes good use of helper functions for code reuse.

These changes significantly enhance the JWT validation capabilities, allowing for more precise audience verification scenarios.


130-199: LGTM: Improved handling of time-based claims

The changes to the time-based claim functions (GetExpirationTime, VerifyExpirationTime, GetIssuedAt, VerifyIssuedAt, GetNotBefore, VerifyNotBefore) significantly improve the structure and consistency of the code. The new getter functions and the modified verification functions work together seamlessly to provide robust claim validation.

Key improvements:

  1. Consistent structure across all time-based claims.
  2. Proper handling of unset and not required cases.
  3. Use of validInt64Future and validInt64Past for accurate time comparisons.

These changes enhance the reliability and maintainability of the JWT validation process.


284-294: LGTM: Improved JSON unmarshaling

The modification to the UnmarshalJSON function enhances the flexibility of number parsing in JSON. By using jjson.UnmarshalIntOrFloat, the function can now handle numbers as either integers or floats, which is particularly useful for JWT claims that may contain different number formats.

This change improves the robustness of the JWT parsing process, reducing the likelihood of errors due to unexpected number formats in claims.


297-359: LGTM: Well-implemented helper functions for claim type conversions

The new helper functions (toInt64, toNumericDate, toString, and toClaimsString) are well-implemented and provide robust type conversion capabilities for different claim types. They include proper error handling and clear error messages for invalid types.

These functions enhance the overall reliability of the claim parsing process and promote code reuse across the package. The consistent approach to handling different claim types will make the code easier to maintain and extend in the future.


Line range hint 1-359: Overall assessment: Significant improvements to JWT claim handling

This PR introduces substantial enhancements to the JWT claim handling capabilities in the claims_map.go file. The changes include:

  1. New getter functions for various claim types.
  2. Improved verification functions with consistent error handling.
  3. A comprehensive Valid function for holistic claim validation.
  4. Enhanced JSON unmarshaling to handle different number formats.
  5. New helper functions for type conversions.

These changes significantly improve the flexibility, reliability, and maintainability of the JWT validation process. The consistent structure across different claim types and the use of helper functions promote code reuse and reduce the likelihood of errors.

While the implementation is generally solid, there are a few minor opportunities for further improvement, such as reducing code duplication in some verification functions and enhancing error messages for more detailed debugging.

Overall, this refactoring aligns well with the PR objectives and represents a substantial step forward in the JWT management system.

handler/openid/strategy_jwt.go (10)

46-46: LGTM: Consistent time handling

The change from time.Now().UTC() to jwt.Now() for initializing RequestedAt is a good improvement. It ensures consistency in time handling across JWT-related operations in the codebase.


144-145: LGTM: Improved abstraction for ID token operations

The introduction of jwtClient using jwt.NewIDTokenClient(requester.GetClient()) is a good improvement. It provides a dedicated abstraction for ID token operations, which can lead to better separation of concerns and increased flexibility in token handling.


147-149: LGTM: Improved error handling for maxAge

The changes in maxAge parsing enhance the robustness of the code. By explicitly handling potential parsing errors and defaulting to 0, the code now gracefully handles cases where maxAge is not provided or is invalid.


154-154: LGTM: Practical adjustment to auth_time validation

The addition of a 5-second buffer when checking if auth_time is in the future is a pragmatic improvement. This change accounts for potential minor timing discrepancies that could occur in distributed systems, making the validation more robust in real-world scenarios.


160-164: LGTM: Enhanced nil-safety in maxAge validation

The updates to the maxAge validation logic significantly improve the robustness of the code. By explicitly checking for nil or zero values in claims.AuthTime and claims.RequestedAt, the code now prevents potential nil pointer dereferences and provides more informative error messages.


196-199: LGTM: Updated ID token hint decoding

The modification to use h.Strategy.Decode for decoding the ID token hint is consistent with the earlier change to DefaultStrategy. Passing the jwtClient to the Decode method is a good practice, as it may provide necessary context for the decoding process. This change aligns well with the new strategy-based approach for JWT operations.


207-211: LGTM: Enhanced subject validation for ID token hint

The updates to the subject extraction and validation process for the ID token hint are valuable improvements:

  1. The code now explicitly handles potential errors when extracting the subject from token claims.
  2. It provides more detailed error messages for missing subjects or subject mismatches.

These changes enhance the robustness and debuggability of the ID token hint validation process.


217-218: LGTM: Improved token generation process

The updates to the token generation logic are excellent improvements:

  1. Using jwt.NewNumericDate for the expiration time ensures consistency with JWT standards.
  2. The use of jwt.Now() for authentication and issuance times aligns with earlier changes, maintaining consistency throughout the codebase.
  3. Including the jwtClient in the token encoding process is consistent with the new strategy-based approach.

These changes enhance the consistency and correctness of the token generation process.

Also applies to: 221-221, 225-226, 243-245


Line range hint 1-248: Overall assessment: Significant improvements to JWT handling

The changes in this file represent a substantial improvement to the JWT handling process:

  1. The shift to a strategy-based approach (jwt.Strategy) provides more flexibility and potentially additional functionality.
  2. Consistent use of jwt.Now() and jwt.NewNumericDate() ensures standardized time handling across JWT operations.
  3. Enhanced error handling and validation processes improve the robustness of the code.
  4. The introduction of jwtClient provides better abstraction for ID token operations.

These changes collectively result in a more maintainable, consistent, and robust implementation of OpenID Connect token handling.

To ensure the changes haven't introduced any regressions, please run the existing test suite for this package and verify that all tests pass.


113-113: Approve change to strategy-based approach

The modification of DefaultStrategy to embed jwt.Strategy instead of jwt.Signer is a good architectural decision. It likely provides more flexibility and potentially additional functionality.

Please verify that all methods previously available through jwt.Signer are still accessible or have been appropriately replaced in the new jwt.Strategy. Run the following script to check for any breaking changes:

integration/oidc_explicit_test.go (5)

108-109: Improved JWT claim handling

The changes to RequestedAt and AuthTime fields are beneficial:

  1. Using jwt.Now() for RequestedAt provides a standardized way to set the current time for JWT claims.
  2. Using jwt.NewNumericDate() for AuthTime ensures proper formatting of the time value as a numeric date in the JWT claim.

These modifications align with best practices for working with JWT claims and improve consistency across the codebase.


133-134: Consistent application of JWT claim updates

The changes here are identical to those in the previous test case, demonstrating consistent application of the new JWT claim handling methods across multiple test scenarios. This consistency is commendable and helps maintain the integrity of the test suite.


146-147: Continued consistency in JWT claim updates

The changes in this test case maintain the same pattern of updates to RequestedAt and AuthTime fields. This ongoing consistency across different test scenarios is excellent, as it ensures that all test cases are updated to use the new JWT claim handling methods uniformly.


176-177: Appropriate JWT claim updates with intentional time difference

The changes in these test cases maintain consistency with the new JWT claim handling methods:

  1. RequestedAt is set using jwt.Now(), consistent with previous updates.
  2. AuthTime is set using jwt.NewNumericDate(time.Now().Add(-time.Minute)), which intentionally sets the authentication time to one minute in the past.

This variation in AuthTime is appropriate for these specific test cases, which appear to be testing scenarios where the authentication occurred in the past. The use of JWT-specific methods continues to align with best practices while allowing for different time scenarios in the tests.

Also applies to: 190-191


Line range hint 1-238: Summary of JWT claim handling improvements

The changes in this file consistently update the handling of JWT claims across multiple test cases in the TestOpenIDConnectExplicitFlow function. These updates offer several benefits:

  1. Standardization: Using jwt.Now() and jwt.NewNumericDate() provides a more standardized approach to handling time-related JWT claims.
  2. Consistency: The changes are applied uniformly across different test scenarios, ensuring consistent behavior.
  3. Improved readability: The use of JWT-specific methods makes the intention of the code clearer.

While these changes are positive, there's an opportunity to further improve the code by refactoring the repeated claim creation into a helper function, as suggested earlier. This refactoring would reduce duplication and make future updates easier to manage.

Overall, these changes represent a significant improvement in the handling of JWT claims in the test suite, enhancing both the quality and maintainability of the tests.

token/jwt/claims_id_token.go (5)

26-29: LGTM: Improved type consistency for time-related fields

The change from time.Time to *NumericDate for ExpirationTime, IssuedAt, RequestedAt, and AuthTime fields improves JSON compatibility and allows for explicit nil checks. This change aligns well with JWT standards and enhances the flexibility of the IDTokenClaims struct.


141-171: LGTM: Well-implemented safe getter methods for time values

The new "Safe" getter methods (GetExpirationTimeSafe, GetIssuedAtSafe, GetAuthTimeSafe, and GetRequestedAtSafe) provide a convenient and safe way to access time values without the need for nil checks in the calling code. The consistent implementation and the use of time.Unix(0, 0).UTC() as a default value for unset times is a good practice.


Line range hint 266-348: LGTM: Well-updated ToMap method

The ToMap method has been successfully updated to handle the new *NumericDate types and now uses constants for claim keys. These changes improve consistency with the struct field updates and enhance maintainability. The method correctly handles nil cases for time-related fields, ensuring proper JSON serialization.


350-353: LGTM: Useful addition of ToMapClaims method

The new ToMapClaims method provides a convenient way to get a jwt-go MapClaims representation of the IDTokenClaims. Its implementation as a wrapper around ToMap is appropriate and maintains consistency with the existing codebase.


Line range hint 1-384: Overall: Significant improvements to JWT claim handling with some refactoring opportunities

The changes to token/jwt/claims_id_token.go greatly enhance the robustness and flexibility of JWT claim handling. The introduction of *NumericDate types, new validation methods, and safe getters improve the overall functionality and type safety of the package.

Key improvements:

  1. Updated IDTokenClaims struct with *NumericDate types
  2. New getter methods implementing the Claims interface
  3. Comprehensive Valid method for claim validation
  4. Safe getter methods for time-related fields
  5. Implementation of UnmarshalJSON for flexible JSON parsing
  6. Updates to ToMap method and addition of ToMapClaims

Recommendations:

  1. Refactor the Valid method to improve readability and maintainability
  2. Refactor the UnmarshalJSON method to reduce complexity and improve error handling
  3. Address the potential security issue in error messages containing claim values

These changes significantly improve the JWT handling capabilities of the package while maintaining compatibility with existing interfaces.

handler/oauth2/flow_generic_code_token.go (3)

134-134: Improved precision in access token expiration time.

The change from Round(time.Second) to Truncate(jwt.TimePrecision) enhances the consistency of token expiration times. This adjustment ensures that the expiration time aligns precisely with the JWT library's time precision, potentially improving token validation accuracy.


138-138: Consistent precision applied to refresh token expiration time.

This change mirrors the modification made to the access token expiration time, ensuring consistency in how both token types are handled. The use of Truncate(jwt.TimePrecision) aligns the refresh token's expiration time with the JWT library's time precision, maintaining a uniform approach to token management.


134-138: Standardized time precision for JWT token expiration.

The changes in this file consistently apply Truncate(jwt.TimePrecision) to both access and refresh token expiration times. This standardization improves the precision and consistency of token handling, aligning with the JWT library's time precision. The modifications enhance the overall robustness of the token management system without introducing any apparent risks or inconsistencies.

handler/oauth2/flow_refresh.go (1)

150-150: Approve change with verification request.

The change from Round(time.Second) to Truncate(jwt.TimePrecision) for setting the access token expiration time is appropriate. It ensures consistent time precision across the JWT handling.

To ensure this change doesn't introduce any unexpected behavior, please verify:

  1. The value of jwt.TimePrecision and its impact on token expiration.
  2. Any dependent systems that might be affected by this change in precision.

Run the following script to check the jwt.TimePrecision value:

handler/rfc8628/user_authorize_handler_test.go (5)

41-41: Improved precision control for JWT expiration times

The change from Round(time.Second) to Truncate(jwt.TimePrecision) is a good improvement. It ensures consistent behavior by always rounding down to the nearest jwt.TimePrecision and allows for more flexible precision control. This aligns with best practices for handling JWT expiration times.


247-247: Consistent improvement in JWT expiration time handling

This change mirrors the improvement made earlier, replacing Round(time.Second) with Truncate(jwt.TimePrecision). It's good to see this consistency maintained across different test setup functions, ensuring uniform behavior in JWT expiration time handling throughout the test suite.


404-404: Consistent JWT time handling in error case testing

The change to use Truncate(jwt.TimePrecision) is consistently applied here as well. It's particularly important to maintain this consistency in error case testing, as it ensures that the precise time handling doesn't inadvertently affect the outcome of these edge case tests.


447-447: Critical consistency in expired token testing

The use of Truncate(jwt.TimePrecision) is correctly applied in this test case for expired user codes. This consistency is particularly crucial here, as precise time handling directly impacts the behavior of expiration checks. This change ensures that the expiration logic is tested with the same precision as it would be in production code.


Line range hint 41-447: Comprehensive improvement in JWT time precision across test suite

The changes made throughout this file represent a systematic improvement in handling JWT expiration times. By consistently using Truncate(jwt.TimePrecision) instead of Round(time.Second), the test suite now more accurately reflects the behavior of the production code in terms of time precision. This modification enhances the reliability of tests across various scenarios, including normal operations, error cases, and expiration checks.

These changes contribute to more robust and precise testing of the RFC8628 user authorization handler, which should lead to increased confidence in the correctness of the JWT-related functionality.

handler/oauth2/flow_authorize_code_token_test.go (1)

466-467: LGTM! Improved precision in token expiration time assertions.

The change from Round(time.Second) to Truncate(jwt.TimePrecision) improves the accuracy of the token expiration time assertions. This modification aligns the test with the precision used in the JWT package, ensuring more consistent and precise time comparisons in the tests.

handler/oauth2/flow_refresh_test.go (7)

7-7: LGTM: JWT package import added

The addition of the JWT package import is consistent with the changes described in the summary, indicating the use of JWT-related functionality in the tests.


118-118: Improved timestamp precision handling

The change from Round(time.Hour) to Truncate(time.Hour) for the RequestedAt field improves consistency in timestamp handling. This modification ensures that the timestamp is always rounded down to the nearest hour, which can prevent potential issues with time comparisons in tests.


143-143: Consistent timestamp handling across test cases

The change from Round(time.Hour) to Truncate(time.Hour) for the RequestedAt field is consistent with the previous modification. This ensures uniform timestamp handling across different test cases, improving the overall reliability of the tests.


169-169: Standardized timestamp handling throughout the test suite

The consistent change from Round(time.Hour) to Truncate(time.Hour) for the RequestedAt field across multiple test cases demonstrates a standardized approach to timestamp handling. This uniformity enhances the reliability and predictability of the test suite.


179-179: Improved JWT expiration time assertion

The change from Round(time.Second) to Truncate(jwt.TimePrecision) for asserting the access token expiration time aligns the test with the standardized JWT time precision. This modification ensures that the expected expiration time is consistent with the actual token generation, improving the accuracy of JWT-related tests.


180-180: Consistent JWT expiration time handling for refresh tokens

The change from Round(time.Second) to Truncate(jwt.TimePrecision) for asserting the refresh token expiration time is consistent with the previous modification for access tokens. This ensures uniform handling of JWT time precision across different token types, enhancing the overall consistency and reliability of the JWT-related tests.


7-7: Overall improvement in timestamp handling and JWT assertions

The changes in this file consistently enhance the handling of timestamps and JWT-related assertions across multiple test cases. The standardization of using Truncate instead of Round for timestamps and the adoption of jwt.TimePrecision for token expiration assertions improve the accuracy and reliability of the test suite. These modifications align well with the goal of refining JWT management and ensure more consistent and predictable test behavior.

Also applies to: 118-118, 143-143, 169-169, 179-180

token/jwt/validate.go (6)

8-9: LGTM!

The type definition for ClaimValidationOption correctly establishes a functional option type for configuring claim validation options.


10-19: LGTM!

The ClaimValidationOptions struct is well-defined and includes all necessary fields for JWT claim validation.


69-81: LGTM!

The verifyAud function correctly verifies the audience claim against the expected value using constant-time comparison to prevent timing attacks.


83-97: LGTM!

The verifyAudAny function properly checks if any of the expected audience values match the audience claim, using constant-time comparison.


118-126: LGTM!

The validInt64Future function correctly checks if a given time value is in the future relative to the current time.


128-135: LGTM!

The validInt64Past function correctly checks if a given time value is in the past or present relative to the current time.

token/jwt/token.go (1)

20-36: New constructors New and NewWithClaims are implemented correctly

The New and NewWithClaims functions properly initialize the Token struct with appropriate default values and provided parameters, ensuring that all fields are correctly set up for further use.

))
resp.SetExpiresIn(int64(
time.Now().Round(time.Second).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(),
time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Approve Truncate(jwt.TimePrecision) usage, but check Second() method

The use of Truncate(jwt.TimePrecision) aligns with the standardization of time precision for JWT operations, which is good. However, there's a potential issue at the end of the line.

The method Second() is used instead of Seconds(). This might be a typo and could lead to unexpected behavior. Please verify and correct if necessary:

-		time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(),
+		time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Seconds(),

Also, consider adding a comment explaining the rationale behind using Truncate(jwt.TimePrecision) to improve code maintainability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Second(),
time.Now().Truncate(jwt.TimePrecision).Add(oauth2.Config.GetRFC8628CodeLifespan(context.TODO())).Seconds(),

@@ -25,10 +26,10 @@
resp.SetUserCode("AAAA")
resp.SetDeviceCode("BBBB")
resp.SetInterval(int(
oauth2.Config.GetRFC8628TokenPollingInterval(context.TODO()).Round(time.Second).Seconds(),
oauth2.Config.GetRFC8628TokenPollingInterval(context.TODO()).Truncate(jwt.TimePrecision).Seconds(),
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Update remaining time calculation to use Truncate(jwt.TimePrecision)

There is still an instance where Round(time.Second) is used in handler/rfc7523/handler.go. To maintain consistency in time precision across the codebase, please update this to use Truncate(jwt.TimePrecision) as well.

  • File: handler/rfc7523/handler.go
  • Line: SetExpiresAt for oauth2.AccessToken
🔗 Analysis chain

Approve change to use Truncate(jwt.TimePrecision)

The modification from Round(time.Second) to Truncate(jwt.TimePrecision) aligns with standardizing time precision for JWT operations. This change is likely part of a broader effort to ensure consistent time handling across the codebase.

To ensure this change doesn't introduce unexpected behavior, please verify:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent use of jwt.TimePrecision in time calculations

# Test: Search for other occurrences of jwt.TimePrecision
rg --type go "jwt\.TimePrecision"

# Test: Check for any remaining uses of Round(time.Second) that might need updating
rg --type go "Round\(time\.Second\)"

Length of output: 5292

@@ -38,7 +39,7 @@ func (d *DeviceAuthorizeHandler) HandleRFC8628DeviceAuthorizeEndpointRequest(ctx
dar.SetDeviceCodeSignature(deviceCodeSignature)
dar.SetUserCodeSignature(userCodeSignature)

expireAt := time.Now().UTC().Add(d.Config.GetRFC8628CodeLifespan(ctx)).Round(time.Second)
expireAt := time.Now().UTC().Add(d.Config.GetRFC8628CodeLifespan(ctx)).Truncate(jwt.TimePrecision)
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Issue Detected: Remaining usage of .Round(time.Second) found in handler/rfc7523/handler.go.

  • Line 42 still uses .Round(time.Second) instead of .Truncate(jwt.TimePrecision).

Please update this to ensure consistent expiration time calculations across the codebase.

🔗 Analysis chain

LGTM: Expiration time calculation change improves consistency.

The modification from Round(time.Second) to Truncate(jwt.TimePrecision) aligns the expiration time calculation with the JWT package's standard time precision. This change enhances consistency in token handling across the application.

To ensure this change is consistently applied throughout the codebase, please run the following verification script:

This script will help identify any remaining instances of the old pattern and confirm the usage of the new pattern across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of jwt.TimePrecision in expiration time calculations

# Search for time.Now().UTC().Add(...).Round(time.Second) pattern
echo "Searching for remaining instances of .Round(time.Second) in time calculations:"
rg --type go 'time\.Now\(\)\.UTC\(\)\.Add\([^)]+\)\.Round\(time\.Second\)'

# Search for time.Now().UTC().Add(...).Truncate(jwt.TimePrecision) pattern
echo "Verifying usage of .Truncate(jwt.TimePrecision) in time calculations:"
rg --type go 'time\.Now\(\)\.UTC\(\)\.Add\([^)]+\)\.Truncate\(jwt\.TimePrecision\)'

Length of output: 2566

@@ -49,7 +50,7 @@ func (c *OpenIDConnectRefreshHandler) HandleTokenEndpointRequest(ctx context.Con
}

// We need to reset the expires at value as this would be the previous expiry.
sess.IDTokenClaims().ExpiresAt = time.Time{}
sess.IDTokenClaims().ExpirationTime = jwt.NewNumericDate(time.Time{})
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Setting ExpirationTime to zero will cause token validation to fail as it is marked as mandatory.

Consider assigning a valid future time to ExpirationTime to ensure tokens are valid upon issuance.

🔗 Analysis chain

Verify the intention of setting ExpirationTime to an empty time.

The change from ExpiresAt to ExpirationTime aligns with the JWT standard claims, which is good. However, setting ExpirationTime to jwt.NewNumericDate(time.Time{}) will result in a token that is immediately expired or invalid.

Is this intentional? If not, consider setting it to a future time based on the token's intended lifespan.

To confirm the impact of this change, let's check how ExpirationTime is used elsewhere in the codebase:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check usage of ExpirationTime in ID token claims

# Search for ExpirationTime usage in ID token claims
rg --type go -i 'IDTokenClaims.*ExpirationTime'

Length of output: 937


Script:

#!/bin/bash
# Description: Check how ExpirationTime is used in token validation and expiration logic

# Search for usages of ExpirationTime in validation or comparison contexts
rg --type go -i 'ExpirationTime.*(After|Before|IsZero|Validate|Check)'

Length of output: 505


rtLifespan := oauth2.GetEffectiveLifespan(request.GetClient(), oauth2.GrantTypePassword, oauth2.RefreshToken, c.Config.GetRefreshTokenLifespan(ctx))
if rtLifespan > -1 {
request.GetSession().SetExpiresAt(oauth2.RefreshToken, time.Now().UTC().Add(rtLifespan).Round(time.Second))
request.GetSession().SetExpiresAt(oauth2.RefreshToken, time.Now().UTC().Add(rtLifespan).Truncate(jwt.TimePrecision))
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consistent precision change for refresh token expiration.

The modification to use Truncate(jwt.TimePrecision) for the refresh token expiration aligns with the change made for the access token. This ensures consistency in how token expiration times are calculated across different token types.

Consider refactoring the expiration time calculation into a separate function to reduce code duplication and ensure consistent behavior. For example:

func calculateTokenExpiration(baseTime time.Time, lifespan time.Duration) time.Time {
    return baseTime.Add(lifespan).Truncate(jwt.TimePrecision)
}

Then, you can use this function for both access and refresh token expiration calculations:

request.GetSession().SetExpiresAt(oauth2.AccessToken, calculateTokenExpiration(time.Now().UTC(), atLifespan))
request.GetSession().SetExpiresAt(oauth2.RefreshToken, calculateTokenExpiration(time.Now().UTC(), rtLifespan))

This refactoring would make the code more maintainable and reduce the risk of inconsistencies in future modifications.

@@ -106,11 +107,11 @@ func (c *AuthorizeExplicitGrantHandler) HandleTokenEndpointRequest(ctx context.C
request.SetID(authorizeRequest.GetID())

atLifespan := oauth2.GetEffectiveLifespan(request.GetClient(), oauth2.GrantTypeAuthorizationCode, oauth2.AccessToken, c.Config.GetAccessTokenLifespan(ctx))
request.GetSession().SetExpiresAt(oauth2.AccessToken, time.Now().UTC().Add(atLifespan).Round(time.Second))
request.GetSession().SetExpiresAt(oauth2.AccessToken, time.Now().UTC().Add(atLifespan).Truncate(jwt.TimePrecision))
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Inconsistent usage of time rounding detected.

Found Round(time.Second) in handler/rfc7523/handler.go. Please replace it with Truncate(jwt.TimePrecision) to ensure consistent time precision for token expiration across the codebase.

🔗 Analysis chain

LGTM. Verify consistent usage of jwt.TimePrecision.

The change from Round to Truncate(jwt.TimePrecision) for setting the access token expiration time is a good improvement for consistency. It ensures that all token expiration times are set with the same precision.

To ensure this change is applied consistently, please run the following script to check for any remaining usage of Round method for token expiration times:

This will help identify any inconsistencies in time precision handling across the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usage of Round method for token expiration times

# Test: Search for Round method usage in time-related operations
rg --type go 'Round\(time\.Second\)' -g '!vendor'

Length of output: 173

Comment on lines +62 to +140
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
vopts := &ClaimValidationOptions{}

for _, opt := range opts {
opt(vopts)
}

var now int64

if vopts.timef != nil {
now = vopts.timef().UTC().Unix()
} else {
now = TimeFunc().UTC().Unix()
}

vErr := new(ValidationError)

var date *NumericDate

if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}

if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}

if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}

var str string

if len(vopts.iss) != 0 {
if str, err = c.GetIssuer(); err != nil {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.iss, true) {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
}
}

if len(vopts.sub) != 0 {
if str, err = c.GetSubject(); err != nil {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.sub, true) {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorSubject
}
}

var aud ClaimStrings

if len(vopts.aud) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}

if len(vopts.audAll) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}

if vErr.valid() {
return nil
}

return vErr
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

LGTM: Comprehensive claim validation implementation

The Valid method provides a thorough validation of claims, covering expiration, issuance, audience, and other important checks. The use of a ValidationError struct for accumulating errors is a good practice.

Consider refactoring this method to improve readability:

  1. Extract the validation logic for each claim type into separate methods.
  2. Use a slice of validation functions to iterate over, reducing the method's complexity.

Example refactoring:

func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) error {
	vopts := &ClaimValidationOptions{}
	for _, opt := range opts {
		opt(vopts)
	}

	now := getValidationTime(vopts)
	vErr := new(ValidationError)

	validations := []func(*ValidationError, int64, *ClaimValidationOptions){
		c.validateExpiration,
		c.validateIssuedAt,
		c.validateNotBefore,
		c.validateIssuer,
		c.validateSubject,
		c.validateAudience,
	}

	for _, validate := range validations {
		validate(vErr, now, vopts)
	}

	if vErr.valid() {
		return nil
	}

	return vErr
}

func (c IDTokenClaims) validateExpiration(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) {
	if date, err := c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
		vErr.Inner = errors.New("Token is expired")
		vErr.Errors |= ValidationErrorExpired
	}
}

// Implement other validation methods similarly...

This refactoring would make the Valid method more modular and easier to maintain.

Comment on lines +99 to +116
func verifyAudAll(aud []string, cmp []string, required bool) bool {
if len(aud) == 0 {
return !required
}

outer:
for _, c := range cmp {
for _, a := range aud {
if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
continue outer
}
}

return false
}

return true
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor 'verifyAudAll' to improve readability

The use of the label outer and the continue statement in verifyAudAll can be avoided to enhance code readability. Consider refactoring the function to eliminate the label and use more straightforward control flow.

Apply this diff to refactor the function:

 func verifyAudAll(aud []string, cmp []string, required bool) bool {
 	if len(aud) == 0 {
 		return !required
 	}

-outer:
-	for _, c := range cmp {
-		for _, a := range aud {
-			if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
-				continue outer
-			}
-		}
-
-		return false
-	}
+	for _, c := range cmp {
+		matched := false
+		for _, a := range aud {
+			if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
+				matched = true
+				break
+			}
+		}
+		if !matched {
+			return false
+		}
+	}

 	return true
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func verifyAudAll(aud []string, cmp []string, required bool) bool {
if len(aud) == 0 {
return !required
}
outer:
for _, c := range cmp {
for _, a := range aud {
if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
continue outer
}
}
return false
}
return true
}
func verifyAudAll(aud []string, cmp []string, required bool) bool {
if len(aud) == 0 {
return !required
}
for _, c := range cmp {
matched := false
for _, a := range aud {
if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
matched = true
break
}
}
if !matched {
return false
}
}
return true
}

Comment on lines 575 to +584
}
return v
}

func validateTokenType(values []string, header map[string]any) bool {
var (
raw any
ok bool
)

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor suggestion: Simplify the pointer function to avoid reflection

The pointer function currently uses reflection to convert non-pointer values to pointers, which can impact performance and readability.

Consider using a type switch or leveraging Go generics (if using Go 1.18 or later) to avoid reflection:

Option 1: Use a type switch

func pointer(v interface{}) interface{} {
    switch val := v.(type) {
    case *DesiredType:
        return val
    case DesiredType:
        return &val
    default:
        return val
    }
}

Option 2: Use generics (Go 1.18+)

func pointer[T any](v T) *T {
    return &v
}

This refactor improves performance by eliminating reflection and enhances code clarity.

@james-d-elliott james-d-elliott force-pushed the feat-jwe branch 3 times, most recently from 9f810e6 to 1373d68 Compare September 29, 2024 04:13
Copy link

@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: 4

🧹 Outside diff range and nitpick comments (9)
README.md (5)

25-27: Excellent progress on implementing key features!

Great job on completing several important features:

  1. Simplifying internal JWT logic using github.com/golang-jwt/jwt/v5
  2. Implementing internal JWKS logic
  3. Adding JWE support for client authentication and issuance
  4. Implementing key management

These additions significantly enhance the project's capabilities and security.

Consider updating the project's main documentation or creating separate documentation pages to explain how to use these new features, especially the JWE support and key management functionality. This will help users understand and leverage these new capabilities effectively.

Also applies to: 106-106, 109-109


Line range hint 30-54: Comprehensive fixes improve overall reliability

Excellent work on addressing various issues across different flows and functionalities. The fixes cover important areas such as:

  • RFC9068 compliance
  • Argument case-insensitivity
  • Improvements in Refresh, PKCE, and OpenID flows
  • Error handling and response writing

The inclusion of commit references for each fix is very helpful for tracking changes.

To improve readability and organization, consider grouping the fixes by category (e.g., "Authentication Flows", "Error Handling", "Standards Compliance") in the README. This could make it easier for contributors and users to understand the areas of improvement.


Line range hint 56-113: Impressive feature additions enhancing standards compliance and security

Congratulations on implementing a wide range of new features, including:

  1. Support for multiple RFCs (8628, 8693, 9101, 9207)
  2. JWT Secured Authorization Response Mode (JARM)
  3. Improvements in client authentication and revocation flows
  4. Support for s_hash

These additions greatly enhance the project's compliance with OAuth 2.0 and OpenID Connect standards, as well as improve overall security and flexibility.

Given the substantial number of new features, consider the following next steps:

  1. Prioritize the implementation of remaining features, especially those related to security (e.g., RFC8705 for Mutual-TLS Client Authentication).
  2. Create a roadmap or project board to track the progress of remaining features and make it visible to the community.
  3. Consider writing migration guides for users upgrading from previous versions, as these new features may require configuration changes.
🧰 Tools
🪛 LanguageTool

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)


Line range hint 115-131: Significant improvements in dependency management

Great job on streamlining the project's dependencies:

  1. Removal of multiple unnecessary dependencies
  2. Migration to newer versions of critical dependencies

These changes should lead to a more maintainable codebase with potentially improved performance and security.

To further improve dependency management:

  1. Consider adding a go.mod file to the repository (if not already present) to explicitly define and version the project's dependencies.
  2. Document the rationale behind removing each dependency in the commit messages or in a separate document. This will help future maintainers understand the decisions made.
  3. Implement a process for regularly reviewing and updating dependencies to ensure the project stays current with the latest security patches and improvements.

107-107: Minor style improvement suggestion

The README is well-structured and easy to read. However, there's a small style improvement that can be made:

In American English, the abbreviation "etc" should include a period. Please update line 107 to read:

-  - [x] Testing Package (mocks, etc)
+  - [x] Testing Package (mocks, etc.)

This change will ensure consistency with American English style guidelines throughout the document.

🧰 Tools
🪛 LanguageTool

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)

token/jwt/claims_map.go (3)

67-128: LGTM: Comprehensive audience claim handling with a minor suggestion

The new audience-related methods (GetAudience, VerifyAudience, VerifyAudienceAll, and VerifyAudienceAny) provide comprehensive and flexible options for audience verification. The use of ClaimStrings type allows for proper handling of multiple audience values.

Consider adding a brief comment explaining the difference between VerifyAudienceAll and VerifyAudienceAny to improve code documentation.


215-283: LGTM: Comprehensive claim validation method with a suggestion

The new Valid method provides a flexible and comprehensive way to validate multiple claims in one call. It properly handles time-based claims and optional validations, and the use of a ValidationError struct allows for detailed error reporting.

Consider adding a comment explaining the purpose and usage of the ClaimValidationOption type and its related functions to improve code documentation.


308-360: LGTM: Well-implemented helper methods for claim extraction and conversion

The new helper methods (toNumericDate, toString, and toClaimsString) provide type-safe ways to extract and convert different types of claims. The error handling is appropriate, using custom error types. The toClaimsString method handles multiple possible types for the audience claim, which provides good flexibility.

In the toClaimsString method, consider using a type switch instead of multiple type assertions to improve readability and potentially performance.

token/jwt/token.go (1)

247-295: Ensure Consistent Error Wrapping in CompactEncrypted Method

In the CompactEncrypted method, errors are wrapped using errorsx.WithStack, but this pattern is not consistently applied throughout the codebase. For consistency and clarity, consider standardizing error handling practices across all methods.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a020d9b and 1373d68.

📒 Files selected for processing (42)
  • README.md (3 hunks)
  • handler/oauth2/flow_authorize_code_token.go (2 hunks)
  • handler/oauth2/flow_authorize_code_token_test.go (2 hunks)
  • handler/oauth2/flow_authorize_implicit.go (2 hunks)
  • handler/oauth2/flow_generic_code_token.go (2 hunks)
  • handler/oauth2/flow_refresh.go (2 hunks)
  • handler/oauth2/flow_refresh_test.go (11 hunks)
  • handler/oauth2/flow_resource_owner.go (2 hunks)
  • handler/oauth2/flow_resource_owner_test.go (2 hunks)
  • handler/oauth2/strategy_jwt_profile_test.go (8 hunks)
  • handler/openid/flow_device_authorization_test.go (4 hunks)
  • handler/openid/flow_hybrid.go (2 hunks)
  • handler/openid/flow_hybrid_test.go (4 hunks)
  • handler/openid/flow_refresh_token.go (3 hunks)
  • handler/openid/strategy_jwt.go (6 hunks)
  • handler/openid/strategy_jwt_test.go (13 hunks)
  • handler/openid/validator.go (3 hunks)
  • handler/openid/validator_test.go (15 hunks)
  • handler/rfc8628/device_authorize_handler.go (2 hunks)
  • handler/rfc8628/token_endpoint_handler_test.go (2 hunks)
  • handler/rfc8628/user_authorize_handler_test.go (5 hunks)
  • handler/rfc8693/access_token_type_handler.go (2 hunks)
  • handler/rfc8693/custom_jwt_type_handler.go (5 hunks)
  • handler/rfc8693/refresh_token_type_handler.go (2 hunks)
  • integration/oidc_explicit_test.go (6 hunks)
  • internal/test_helpers.go (2 hunks)
  • introspection_response_writer_test.go (1 hunks)
  • rfc8628_device_authorize_write_test.go (2 hunks)
  • testing/mock/client.go (1 hunks)
  • token/jarm/generate.go (2 hunks)
  • token/jwt/claims_id_token.go (7 hunks)
  • token/jwt/claims_id_token_test.go (3 hunks)
  • token/jwt/claims_jarm.go (2 hunks)
  • token/jwt/claims_jarm_test.go (2 hunks)
  • token/jwt/claims_jwt.go (5 hunks)
  • token/jwt/claims_jwt_test.go (5 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/claims_test.go (1 hunks)
  • token/jwt/date.go (1 hunks)
  • token/jwt/token.go (3 hunks)
  • token/jwt/token_test.go (8 hunks)
  • token/jwt/validate.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (39)
  • handler/oauth2/flow_authorize_code_token.go
  • handler/oauth2/flow_authorize_code_token_test.go
  • handler/oauth2/flow_authorize_implicit.go
  • handler/oauth2/flow_generic_code_token.go
  • handler/oauth2/flow_refresh.go
  • handler/oauth2/flow_refresh_test.go
  • handler/oauth2/flow_resource_owner.go
  • handler/oauth2/flow_resource_owner_test.go
  • handler/oauth2/strategy_jwt_profile_test.go
  • handler/openid/flow_device_authorization_test.go
  • handler/openid/flow_hybrid.go
  • handler/openid/flow_hybrid_test.go
  • handler/openid/flow_refresh_token.go
  • handler/openid/strategy_jwt.go
  • handler/openid/strategy_jwt_test.go
  • handler/openid/validator.go
  • handler/openid/validator_test.go
  • handler/rfc8628/device_authorize_handler.go
  • handler/rfc8628/token_endpoint_handler_test.go
  • handler/rfc8628/user_authorize_handler_test.go
  • handler/rfc8693/access_token_type_handler.go
  • handler/rfc8693/custom_jwt_type_handler.go
  • handler/rfc8693/refresh_token_type_handler.go
  • integration/oidc_explicit_test.go
  • internal/test_helpers.go
  • introspection_response_writer_test.go
  • rfc8628_device_authorize_write_test.go
  • testing/mock/client.go
  • token/jarm/generate.go
  • token/jwt/claims_id_token.go
  • token/jwt/claims_id_token_test.go
  • token/jwt/claims_jarm.go
  • token/jwt/claims_jarm_test.go
  • token/jwt/claims_jwt.go
  • token/jwt/claims_jwt_test.go
  • token/jwt/claims_test.go
  • token/jwt/date.go
  • token/jwt/token_test.go
  • token/jwt/validate.go
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)

🔇 Additional comments (11)
token/jwt/claims_map.go (9)

19-41: LGTM: Well-implemented issuer claim handling

The new GetIssuer and VerifyIssuer methods are well-implemented. The VerifyIssuer method's required parameter allows for flexible validation, which is a good practice for handling optional claims.


43-65: LGTM: Consistent implementation of subject claim handling

The GetSubject and VerifySubject methods are implemented consistently with the issuer claim methods. This consistency enhances code maintainability and readability.


131-153: LGTM: Well-implemented expiration time claim handling

The GetExpirationTime and VerifyExpirationTime methods are implemented correctly and consistently with other claim handling methods. The use of NumericDate type and validInt64Future function for verification is appropriate for the expiration time claim.


155-177: LGTM: Consistent implementation of issued at claim handling

The GetIssuedAt and VerifyIssuedAt methods are implemented consistently with other time-based claim methods. The use of validInt64Past for verification is appropriate for the issued at claim.


179-201: LGTM: Consistent implementation of not before claim handling

The GetNotBefore and VerifyNotBefore methods are implemented consistently with other time-based claim methods. The use of validInt64Past for verification is appropriate for the not before claim.


203-213: LGTM: Useful type conversion methods added

The ToMapClaims and ToMap methods provide simple and useful type conversions. The nil check in ToMapClaims is a good practice to prevent nil pointer dereferences.


285-297: LGTM: Improved JSON unmarshaling

The modified UnmarshalJSON method uses a custom JSON decoder with jjson.UnmarshalIntOrFloat, allowing for more flexible number parsing. This improves interoperability. The use of errorsx.WithStack for error handling is a good practice for maintaining the error stack trace.


298-306: LGTM: Safe int64 conversion method added

The new toInt64 method provides a safe way to extract and convert int64 values from claims. It properly handles the case where the claim doesn't exist.


Line range hint 1-360: Overall: Excellent improvements to JWT claim handling

The changes in this file significantly enhance the JWT claim handling capabilities. The new methods provide a comprehensive and flexible way to retrieve, verify, and validate various claim types. The implementation is consistent across different claim types, which improves maintainability and readability.

Key improvements include:

  1. Consistent methods for handling standard claims (iss, sub, aud, exp, iat, nbf).
  2. Flexible audience verification options.
  3. A comprehensive Valid method for validating multiple claims.
  4. Improved JSON unmarshaling for better interoperability.
  5. Type-safe helper methods for claim extraction and conversion.

These changes will likely improve the robustness and usability of the JWT handling in the system.

token/jwt/token.go (2)

117-123: ⚠️ Potential issue

Correct Error Wrapping in Claims Validation

In the error handling for claims validation, there's a logical issue. If the type assertion e, ok := err.(*ValidationError) fails (i.e., ok is false), e will be nil. This results in wrapping a nil error, potentially losing the original error information stored in err.

Apply this diff to properly wrap the original error:

 if err = claims.Valid(); err != nil {
-    if e, ok := err.(*ValidationError); !ok {
-        err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
+    if _, ok := err.(*ValidationError); !ok {
+        err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
     }
     return token, err
 }

Likely invalid or redundant comment.


611-626: 🛠️ Refactor suggestion

Simplify isUnsafeNoneMagicConstant Function for Readability

The isUnsafeNoneMagicConstant function can be simplified to reduce nested type assertions and improve readability.

Consider refactoring the function as follows:

 func isUnsafeNoneMagicConstant(k any) bool {
-    switch key := k.(type) {
-    case unsafeNoneMagicConstant:
-        return true
-    case jose.JSONWebKey:
-        if _, ok := key.Key.(unsafeNoneMagicConstant); ok {
-            return true
-        }
-    case *jose.JSONWebKey:
-        if _, ok := key.Key.(unsafeNoneMagicConstant); ok {
-            return true
-        }
-    }
+    if _, ok := k.(unsafeNoneMagicConstant); ok {
+        return true
+    }
+    if key, ok := k.(jose.JSONWebKey); ok {
+        _, ok := key.Key.(unsafeNoneMagicConstant)
+        return ok
+    }
+    if key, ok := k.(*jose.JSONWebKey); ok {
+        _, ok := key.Key.(unsafeNoneMagicConstant)
+        return ok
+    }
     return false
 }

Likely invalid or redundant comment.

Comment on lines +85 to +94
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
var ve *ValidationError

if errors.As(err, &ve) {
return token, ve
}

return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure Proper Error Handling in Key Function

In the ParseCustomWithClaims function, when keyFunc(token) returns an error, the current logic may not preserve all error information, especially if the error is a custom type implementing error but not *ValidationError.

Consider adjusting the error handling to properly handle all error types:

 if key, err = keyFunc(token); err != nil {
-    var ve *ValidationError
-
-    if errors.As(err, &ve) {
-        return token, ve
-    }
-
-    return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
+    if ve, ok := err.(*ValidationError); ok {
+        return token, ve
+    }
+    return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
 }

This change ensures that if the error is a *ValidationError, it is returned as is; otherwise, it's wrapped appropriately.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
var ve *ValidationError
if errors.As(err, &ve) {
return token, ve
}
return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
}
if key, err = keyFunc(token); err != nil {
if ve, ok := err.(*ValidationError); ok {
return token, ve
}
return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
}

Comment on lines +592 to +609
func validateTokenTypeValue(values []string, raw any) bool {
var (
typ string
ok bool
)

if typ, ok = raw.(string); !ok {
return false
}

for _, t := range values {
if t == typ {
return true
}
}

return false
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Simplify validateTokenTypeValue Function

The validateTokenTypeValue function can be simplified for better readability by returning early upon finding a match.

Refactor the function as follows:

 func validateTokenTypeValue(values []string, raw any) bool {
-    var (
-        typ string
-        ok  bool
-    )
-
-    if typ, ok = raw.(string); !ok {
+    typ, ok := raw.(string)
+    if !ok {
         return false
     }
 
-    for _, t := range values {
-        if t == typ {
-            return true
-        }
-    }
-
-    return false
+    return containsString(values, typ)
 }
 
+func containsString(slice []string, str string) bool {
+    for _, v := range slice {
+        if v == str {
+            return true
+        }
+    }
+    return false
+}

This refactor improves readability and efficiency by eliminating unnecessary variables and simplifying the control flow.

Committable suggestion was skipped due to low confidence.

Comment on lines +358 to +379
vErr.Inner = errors.New("token has an invalid or unverified signature")
vErr.Errors |= ValidationErrorSignatureInvalid
}
h := base64.RawURLEncoding.EncodeToString(hbytes)
b := base64.RawURLEncoding.EncodeToString(bbytes)
return fmt.Sprintf("%v.%v.", h, b), nil
}

func newToken(parsedToken *jwt.JSONWebToken, claims MapClaims) (*Token, error) {
token := &Token{Claims: claims}
if len(parsedToken.Headers) != 1 {
return nil, &ValidationError{text: fmt.Sprintf("only one header supported, got %v", len(parsedToken.Headers)), Errors: ValidationErrorMalformed}
if t.HeaderJWE != nil && (t.KeyAlgorithm != "" || t.ContentEncryption != "") {
var (
typ any
ok bool
)

if typ, ok = t.HeaderJWE[JSONWebTokenHeaderType]; !ok || typ != JSONWebTokenTypeJWT {
vErr.Inner = errors.New("token was encrypted with invalid typ")
vErr.Errors |= ValidationErrorHeaderEncryptionTypeInvalid
}

ttyp := t.Header[JSONWebTokenHeaderType]
cty := t.HeaderJWE[JSONWebTokenHeaderContentType]

if cty != ttyp {
vErr.Inner = errors.New("token was encrypted with a cty value that doesn't match the typ value")
vErr.Errors |= ValidationErrorHeaderContentTypeInvalidMismatch
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid Overwriting Errors in Header Validation

In the Valid method, errors are being assigned to vErr.Inner multiple times, which may overwrite previous errors and lead to loss of information. This could make debugging difficult as only the last error is retained.

Consider accumulating errors or appending messages to provide comprehensive feedback:

 if !t.valid {
-    vErr.Inner = errors.New("token has an invalid or unverified signature")
+    vErr = vErr.Append("token has an invalid or unverified signature")
     vErr.Errors |= ValidationErrorSignatureInvalid
 }
 
 // ...
 
 if condition {
-    vErr.Inner = errors.New("another validation error")
+    vErr = vErr.Append("another validation error")
     vErr.Errors |= ValidationErrorSomeOtherError
 }

Ensure that ValidationError supports error accumulation, perhaps by maintaining a slice of errors.

Committable suggestion was skipped due to low confidence.

Comment on lines +130 to +147
// Token represets a JWT Token.
type Token struct {
KeyID string
SignatureAlgorithm jose.SignatureAlgorithm // alg (JWS)
EncryptionKeyID string
KeyAlgorithm jose.KeyAlgorithm // alg (JWE)
ContentEncryption jose.ContentEncryption // enc (JWE)
CompressionAlgorithm jose.CompressionAlgorithm // zip (JWE)

Header map[string]any
HeaderJWE map[string]any

Claims Claims

// Valid informs if the token was verified against a given verification key
parsedToken *jwt.JSONWebToken

valid bool
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add JSON Tags to Token Struct Fields

The fields in the Token struct lack JSON struct tags. If serialization to JSON is required for logging or other purposes, adding explicit JSON tags ensures correct field names and can help avoid accidental exposure of sensitive information.

Consider adding JSON tags to the struct fields:

 type Token struct {
-    KeyID                string
-    SignatureAlgorithm   jose.SignatureAlgorithm // alg (JWS)
-    EncryptionKeyID      string
-    KeyAlgorithm         jose.KeyAlgorithm         // alg (JWE)
-    ContentEncryption    jose.ContentEncryption    // enc (JWE)
-    CompressionAlgorithm jose.CompressionAlgorithm // zip (JWE)
+    KeyID                string                   `json:"kid,omitempty"`
+    SignatureAlgorithm   jose.SignatureAlgorithm  `json:"alg,omitempty"` // alg (JWS)
+    EncryptionKeyID      string                   `json:"enc_kid,omitempty"`
+    KeyAlgorithm         jose.KeyAlgorithm        `json:"enc_alg,omitempty"`        // alg (JWE)
+    ContentEncryption    jose.ContentEncryption   `json:"enc,omitempty"`    // enc (JWE)
+    CompressionAlgorithm jose.CompressionAlgorithm `json:"zip,omitempty"` // zip (JWE)

     Header    map[string]any       `json:"header,omitempty"`
     HeaderJWE map[string]any       `json:"header_jwe,omitempty"`

     Claims Claims                  `json:"claims,omitempty"`

     parsedToken *jwt.JSONWebToken  `json:"-"`

     valid bool                     `json:"-"`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Token represets a JWT Token.
type Token struct {
KeyID string
SignatureAlgorithm jose.SignatureAlgorithm // alg (JWS)
EncryptionKeyID string
KeyAlgorithm jose.KeyAlgorithm // alg (JWE)
ContentEncryption jose.ContentEncryption // enc (JWE)
CompressionAlgorithm jose.CompressionAlgorithm // zip (JWE)
Header map[string]any
HeaderJWE map[string]any
Claims Claims
// Valid informs if the token was verified against a given verification key
parsedToken *jwt.JSONWebToken
valid bool
}
// Token represets a JWT Token.
type Token struct {
KeyID string `json:"kid,omitempty"`
SignatureAlgorithm jose.SignatureAlgorithm `json:"alg,omitempty"` // alg (JWS)
EncryptionKeyID string `json:"enc_kid,omitempty"`
KeyAlgorithm jose.KeyAlgorithm `json:"enc_alg,omitempty"` // alg (JWE)
ContentEncryption jose.ContentEncryption `json:"enc,omitempty"` // enc (JWE)
CompressionAlgorithm jose.CompressionAlgorithm `json:"zip,omitempty"` // zip (JWE)
Header map[string]any `json:"header,omitempty"`
HeaderJWE map[string]any `json:"header_jwe,omitempty"`
Claims Claims `json:"claims,omitempty"`
parsedToken *jwt.JSONWebToken `json:"-"`
valid bool `json:"-"`
}

@james-d-elliott james-d-elliott force-pushed the feat-jwe branch 2 times, most recently from 0c03ce9 to 7fac454 Compare October 3, 2024 11:23
Copy link

@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.

Caution

Inline review comments failed to post

Actionable comments posted: 22

🧹 Outside diff range and nitpick comments (32)
token/jwt/claims_jarm_test.go (2)

16-20: LGTM! Consider adding a comment for clarity.

The changes to IssuedAt and ExpirationTime fields improve consistency in time handling. The use of Now() and NewNumericDate() functions suggests a standardized approach to managing time precision in JWT claims.

Consider adding a brief comment explaining the rationale behind using Now() and NewNumericDate(), especially if these are custom functions. This would help other developers understand the importance of consistent time handling in JWT claims.


47-49: LGTM! Consider enhancing test readability.

The changes to use ExpirationTime and NewNumericDate() are consistent with earlier modifications, ensuring standardized time handling in JWT claims.

To improve test readability, consider extracting the time calculations into named variables. For example:

validTime := NewNumericDate(time.Now().Add(time.Hour))
invalidTime := NewNumericDate(time.Now().Add(-2 * time.Hour))

assert.Nil(t, (&JARMClaims{ExpirationTime: validTime}).ToMapClaims().Valid())
assert.NotNil(t, (&JARMClaims{ExpirationTime: invalidTime}).ToMapClaims().Valid())

This change would make the test's intent clearer and easier to maintain.

token/jwt/date.go (1)

68-76: Consider a more descriptive method name for Int64.

While the Int64 method is well-implemented and handles edge cases correctly, its name might not fully convey its purpose. Consider renaming it to something more descriptive, such as UnixTimestamp or SecondsSinceEpoch.

Here's a suggested improvement:

-func (date *NumericDate) Int64() (val int64) {
+func (date *NumericDate) UnixTimestamp() (val int64) {
     if date == nil {
         return 0
     }
     return date.UTC().Truncate(TimePrecision).Unix()
 }
token/jwt/claims_jwt.go (3)

229-239: LGTM: Improved time conversion with error handling

The new toTime function is a good addition. It centralizes time conversion logic and improves type safety by handling various input types. The use of toInt64 further abstracts the conversion process.

One minor suggestion:

Consider using a more descriptive variable name than v for the input parameter to improve readability. For example:

-func toTime(v any, def time.Time) (t time.Time, ok bool) {
+func toTime(value any, def time.Time) (t time.Time, ok bool) {
   // ... rest of the function
-  if value, ok = toInt64(v); ok {
+  if value, ok = toInt64(value); ok {
     // ... rest of the function
   }
}

241-268: LGTM: Robust int64 conversion function

The toInt64 function is a valuable addition. It centralizes int64 conversion logic and handles various numeric types, including json.Number, which is particularly useful for JSON-based token claims. The boolean return value allows callers to handle conversion failures effectively.

One minor suggestion for improvement:

Consider adding a case for uint64 to handle unsigned integers:

func toInt64(v any) (val int64, ok bool) {
   var err error

   switch t := v.(type) {
   // ... existing cases ...
+  case uint64:
+      if t <= math.MaxInt64 {
+          return int64(t), true
+      }
+      return 0, false
   }

   return 0, false
}

This addition would make the function more comprehensive in handling different integer types.


270-304: LGTM: Comprehensive NumericDate conversion function

The toNumericDate function is a well-implemented addition. It centralizes NumericDate conversion logic, handles various types, and consistently treats nil and zero values. The use of newNumericDateFromSeconds suggests that this type represents Unix timestamps, which is appropriate for JWT claims.

A minor suggestion for improvement:

Consider adding a case for time.Time to directly handle time objects:

func toNumericDate(v any) (date *NumericDate, err error) {
   switch value := v.(type) {
   // ... existing cases ...
+  case time.Time:
+      return newNumericDateFromSeconds(float64(value.Unix())), nil
   }

   return nil, newError("value has invalid type", ErrInvalidType)
}

This addition would make the function more versatile by directly accepting time.Time objects, which are commonly used in Go for time representation.

README.md (5)

Line range hint 11-24: Consider adding version numbers for clarity.

To help users understand when certain changes were implemented, consider adding version numbers next to completed items. For example:

- [x] Module path changed from `github.com/ory/fosite` to `authelia.com/provider/oauth2`. (v1.0.0)

This would make it easier for users to track which version introduced specific changes.


25-27: Clarify the status of JWT and JWKS implementations.

The items for simplifying internal JWT logic and implementing internal JWKS logic are marked as completed. Consider adding brief notes on what libraries or methods are now being used, or link to relevant documentation for these implementations.


Line range hint 80-106: Add links to RFCs for better reference.

For the RFC implementations listed (e.g., RFC8628, RFC8693, RFC8705, etc.), consider adding direct links to the RFC documents. This would make it easier for users to reference the specific standards being implemented. For example:

- [x] [RFC8628: OAuth 2.0 Device Authorization Grant](https://datatracker.ietf.org/doc/html/rfc8628) support
🧰 Tools
🪛 LanguageTool

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)


Line range hint 115-128: Fix formatting inconsistency in removed dependencies list.

There's a minor formatting inconsistency in the list of removed dependencies. The last item has an extra space at the end of the package name:

- [x] `github.com/golang-jwt/jwt `

Remove the extra space to maintain consistency with the other items in the list.


Line range hint 134-150: LGTM: "Thanks" section provides clear context and acknowledgments.

The "Thanks" section effectively explains the project's relationship to ORY Fosite and provides important clarifications about licensing and maintenance. This transparency is valuable for users and contributors.

Additionally, there's a minor style improvement to make:

In the line:

- Plan to continue to contribute back to te ORY fosite and related projects.

Correct "te" to "the" and consider adding a period at the end of each bullet point for consistency with American English style:

- Plan to continue to contribute back to the ORY fosite and related projects.
handler/oauth2/strategy_jwt_profile_test.go (2)

58-85: LGTM: Well-structured test case for invalid JWT type

The new jwtInvalidTypCase function is a valuable addition for testing error handling with invalid JWT types. It follows the established pattern of other test case functions in this file.

Consider adding a comment explaining that this function intentionally sets an invalid "typ" claim in the JWT header for testing purposes.

var jwtInvalidTypCase = func(tokenType oauth2.TokenType) *oauth2.Request {
+	// This function intentionally sets an invalid "typ" claim in the JWT header for testing purposes
	r := &oauth2.Request{
		// ... (rest of the function remains unchanged)
	}
	return r
}

147-150: LGTM: Improved test determinism with parameterized time

The addition of the now parameter to jwtExpiredCase and the use of relative time offsets significantly improve test determinism and flexibility. This change eliminates potential flakiness caused by using the current time in tests.

Consider adding a comment explaining the purpose of this change to help future maintainers understand the rationale.

+// jwtExpiredCase generates a request with an expired JWT token.
+// The 'now' parameter allows for deterministic testing of expiration logic.
var jwtExpiredCase = func(tokenType oauth2.TokenType, now time.Time) *oauth2.Request {
	// ... (rest of the function remains unchanged)
}

Also applies to: 159-161, 168-168

token/jwt/claims_map.go (6)

26-41: LGTM with suggestion: VerifyIssuer method implementation

The VerifyIssuer method is well-implemented, correctly handling cases where the issuer is unset and not required. It uses the GetIssuer method and validString helper function for consistency.

However, consider improving error handling:

 func (m MapClaims) VerifyIssuer(cmp string, required bool) (ok bool) {
 	var (
 		iss string
 		err error
 	)

 	if iss, err = m.GetIssuer(); err != nil {
-		return false
+		return false // Consider logging the error or returning it
 	}

 	if iss == "" {
 		return !required
 	}

 	return validString(iss, cmp, required)
 }

This change would allow for better error tracking and debugging if needed.


44-65: LGTM with suggestion: GetSubject and VerifySubject methods

Both GetSubject and VerifySubject methods are well-implemented and consistent with their issuer counterparts. They correctly use helper methods for string conversion and validation.

For VerifySubject, consider the same error handling improvement as suggested for VerifyIssuer:

 func (m MapClaims) VerifySubject(cmp string, required bool) (ok bool) {
 	var (
 		sub string
 		err error
 	)

 	if sub, err = m.GetSubject(); err != nil {
-		return false
+		return false // Consider logging the error or returning it
 	}

 	if sub == "" {
 		return !required
 	}

 	return validString(sub, cmp, required)
 }

This change would allow for better error tracking and debugging if needed.


68-128: LGTM with suggestions: Audience-related methods

The implementation of GetAudience, VerifyAudience, VerifyAudienceAll, and VerifyAudienceAny methods is well-structured and provides great flexibility for various audience validation scenarios. The use of helper methods for claim retrieval and validation ensures consistency.

Suggestions for improvement:

  1. Consider improving error handling in all verification methods, similar to previous suggestions:
 func (m MapClaims) VerifyAudience(cmp string, required bool) (ok bool) {
 	var (
 		aud ClaimStrings
 		err error
 	)

 	if aud, err = m.GetAudience(); err != nil {
-		return false
+		return false // Consider logging the error or returning it
 	}

 	// ... rest of the method
 }

Apply similar changes to VerifyAudienceAll and VerifyAudienceAny.

  1. Consider adding comments to explain the difference between the three verification methods, especially VerifyAudienceAll and VerifyAudienceAny, to make it clearer for developers using these methods.

Great job on providing flexible options for audience verification!


131-200: LGTM with suggestions: Time-based claim methods

The implementation of methods for getting and verifying time-based claims ('exp', 'iat', 'nbf') is consistent and well-structured. The use of the toNumericDate helper and specific validation functions (validInt64Future and validInt64Past) enhances clarity and maintainability.

Suggestions for improvement:

  1. Consider improving error handling in all verification methods, similar to previous suggestions:
 func (m MapClaims) VerifyExpirationTime(cmp int64, required bool) (ok bool) {
 	var (
 		exp *NumericDate
 		err error
 	)

 	if exp, err = m.GetExpirationTime(); err != nil {
-		return false
+		return false // Consider logging the error or returning it
 	}

 	// ... rest of the method
 }

Apply similar changes to VerifyIssuedAt and VerifyNotBefore.

  1. Consider adding a comment explaining the difference between validInt64Future and validInt64Past to make it clear why different functions are used for different time-based claims.

Great job on maintaining consistency across these time-based claim methods!


218-283: LGTM with suggestions: Valid method implementation

The Valid method is well-implemented, providing a comprehensive validation of all standard JWT claims. The use of ValidationError to accumulate multiple validation errors is a good practice, and the flexibility provided through ClaimValidationOption is commendable.

Suggestions for improvement:

  1. Consider adding more descriptive error messages, possibly including the actual and expected values:
 if !m.VerifyExpirationTime(now, vopts.expRequired) {
-	vErr.Inner = errors.New("Token is expired")
+	vErr.Inner = fmt.Errorf("Token is expired (current time: %d)", now)
 	vErr.Errors |= ValidationErrorExpired
 }
  1. Consider adding a comment explaining the purpose and usage of the ClaimValidationOption for better documentation.

  2. You might want to consider returning the ValidationError even if it's not valid, to provide more information about which validations passed and which failed. This could be useful for debugging purposes.

Great job on implementing this comprehensive validation method!


298-360: LGTM with minor suggestion: Helper methods implementation

The helper methods toInt64, toNumericDate, toString, and toClaimsString are well-implemented. They handle various edge cases and provide consistent error handling and type checking. The toClaimsString method, in particular, does a great job handling the complexity of the audience claim.

Suggestion for improvement:

Consider using a consistent error creation method across all helper functions. For example, in toString:

 if value, ok = raw.(string); !ok {
-	return "", newError(fmt.Sprintf("%s is invalid", key), ErrInvalidType)
+	return "", fmt.Errorf("%s is invalid: %w", key, ErrInvalidType)
 }

This would make it consistent with the error creation in toClaimsString.

Great job on these thorough and useful helper methods!

handler/openid/strategy_jwt.go (4)

154-164: LGTM: Enhanced claim validation with minor suggestion

The updates to the authentication time and requested time claim checks are good improvements. They add robustness by ensuring these claims are not nil or zero, and use the new GetAuthTimeSafe() and GetRequestedAtSafe() methods for comparisons.

Consider standardizing the error messages for consistency. For example:

- return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate id token because authentication time claim is required when max_age is set."))
+ return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate ID token: authentication time claim is required when max_age is set."))

- return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate id token because requested at claim is required when max_age is set."))
+ return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate ID token: requested at claim is required when max_age is set."))

171-185: LGTM: Improved prompt parameter handling with minor suggestion

The updates to the prompt parameter checks and related time comparisons are good improvements. They add robustness by ensuring the auth_time claim is not nil or zero, and use the new GetAuthTimeSafe() and GetRequestedAtSafe() methods for comparisons.

Consider improving the formatting of the error messages for better readability:

- WithDebugf("Failed to generate id token because prompt was set to 'none' but auth_time ('%s') happened after the authorization request ('%s') was registered, indicating that the user was logged in during this request which is not allowed.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe()))
+ WithDebugf("Failed to generate ID token: prompt was set to 'none' but auth_time (%s) is after the authorization request (%s), indicating user login during this request which is not allowed.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe()))

- WithDebugf("Failed to generate id token because prompt was set to 'login' but auth_time ('%s') happened before the authorization request ('%s') was registered, indicating that the user was not re-authenticated which is forbidden.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe()))
+ WithDebugf("Failed to generate ID token: prompt was set to 'login' but auth_time (%s) is before the authorization request (%s), indicating the user was not re-authenticated which is forbidden.", claims.GetAuthTimeSafe(), claims.GetRequestedAtSafe()))

196-211: LGTM: Enhanced id_token_hint handling with suggestion

The updates to the id_token_hint parameter handling are good improvements. They add better decoding and validation of the hint token, including proper handling of expired tokens and subject claim comparison.

Consider simplifying the error handling for the subject extraction:

- if subHint, err = tokenHint.Claims.GetSubject(); subHint == "" || err != nil {
-   return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject."))
- } else if subHint != claims.Subject {
-   return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'."))
- }
+ subHint, err := tokenHint.Claims.GetSubject()
+ if err != nil {
+   return "", errorsx.WithStack(oauth2.ErrServerError.WithWrap(err).WithDebug("Failed to extract subject from 'id_token_hint'."))
+ }
+ if subHint == "" {
+   return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject."))
+ }
+ if subHint != claims.Subject {
+   return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Subject from authorization mismatches id token subject from 'id_token_hint'."))
+ }

This change separates the error handling for extraction failure and empty subject, providing more specific error messages.


Line range hint 217-245: LGTM: Improved time-related claim handling and token encoding

The updates to the handling of ExpirationTime, AuthTime, and IssuedAt claims, as well as the new token encoding process, are good improvements. They add important validations and align with the new strategy-based approach.

Consider standardizing the error message for consistency with earlier suggestions:

- return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate id token because expiry claim can not be in the past."))
+ return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Failed to generate ID token: expiry claim cannot be in the past."))
token/jwt/claims_id_token.go (5)

38-60: LGTM: Consistent claim retrieval methods added

The new methods for claim retrieval provide a consistent interface and allow for future extensibility by returning both the claim value and an error. This is a good practice.

However, there's a minor inconsistency in the GetNotBefore method:

func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
	return toNumericDate(ClaimNotBefore)
}

This method uses a toNumericDate function, which is different from the direct field access used in other methods. Consider aligning this with the other methods if NotBefore is intended to be a field of IDTokenClaims.


62-139: LGTM: Comprehensive claim validation with room for improvement

The Valid method provides thorough validation of all standard JWT claims, which is excellent. The use of a ValidationError struct to accumulate errors is a good practice, allowing for multiple validation issues to be reported at once.

However, the method is quite long and could benefit from some refactoring to improve readability and maintainability. Consider breaking it down into smaller, focused functions for each claim type. For example:

func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) error {
	vErr := new(ValidationError)
	now := getNow(vopts)

	c.validateExpiration(vErr, now, vopts)
	c.validateIssuedAt(vErr, now, vopts)
	c.validateNotBefore(vErr, now, vopts)
	c.validateIssuer(vErr, vopts)
	c.validateSubject(vErr, vopts)
	c.validateAudience(vErr, vopts)

	if vErr.valid() {
		return nil
	}
	return vErr
}

func (c IDTokenClaims) validateExpiration(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) {
	// Expiration validation logic
}

// Similar methods for other claim validations

This refactoring would make the Valid method more modular and easier to maintain.


141-171: LGTM: Safe getter methods for time-related claims

The new "Safe" getter methods (GetExpirationTimeSafe, GetIssuedAtSafe, GetAuthTimeSafe, and GetRequestedAtSafe) are a great addition. They provide a safe way to access time-related claims without the risk of nil pointer dereferences, defaulting to the Unix epoch when the field is nil. The consistent use of UTC is also commendable.

For improved consistency, consider extracting the default time creation to a constant or helper function:

var defaultTime = time.Unix(0, 0).UTC()

func (c *IDTokenClaims) GetExpirationTimeSafe() time.Time {
	if c.ExpirationTime == nil {
		return defaultTime
	}
	return c.ExpirationTime.UTC()
}

// Apply similar changes to other Safe getter methods

This small change would make the code more DRY and easier to maintain if the default time needs to be changed in the future.


350-353: LGTM: New ToMapClaims method added

The addition of the ToMapClaims method is a good way to provide a MapClaims representation of the IDTokenClaims. It's currently a simple wrapper around ToMap, which is fine if MapClaims is just an alias for map[string]any.

However, if MapClaims is a different type, consider updating the method to perform the necessary type conversion:

func (c IDTokenClaims) ToMapClaims() MapClaims {
	return MapClaims(c.ToMap())
}

This ensures that the method will work correctly even if MapClaims is not just an alias but a distinct type.


369-379: LGTM: New toNumericDate method for handling additional claims

The toNumericDate method is a good addition for converting additional claims to NumericDate types. It correctly handles the case where the claim doesn't exist in the Extra map.

Consider adding some error handling for the case where the conversion fails:

func (c IDTokenClaims) toNumericDate(key string) (date *NumericDate, err error) {
	v, ok := c.Extra[key]
	if !ok {
		return nil, nil
	}

	date, err = toNumericDate(v)
	if err != nil {
		return nil, fmt.Errorf("failed to convert claim %s to NumericDate: %w", key, err)
	}

	return date, nil
}

This change would provide more context if the conversion fails, making debugging easier.

token/jwt/token_test.go (2)

51-51: LGTM! Consider adding a comment for clarity.

The changes to use CompactSignedString, jwt.ParseSigned, and JSONWebTokenHeaderType are consistent with the updated JWT management system. These modifications improve standardization and potentially leverage more robust parsing from the go-jose library.

Consider adding a brief comment explaining the reason for using CompactSignedString instead of SignedString, to provide context for future developers.

Also applies to: 57-57, 60-60


323-323: LGTM! Consider applying this change consistently.

The replacement of iat with ClaimIssuedAt improves code readability and consistency. This change aligns with best practices for using descriptive names in code.

Consider reviewing the entire codebase to ensure all instances of iat and other abbreviated claim names are replaced with their full, descriptive counterparts (e.g., exp to ClaimExpiresAt, nbf to ClaimNotBefore, etc.) for consistency.

Also applies to: 328-328

token/jwt/token.go (2)

130-147: Add comments to new fields in Token struct

The Token struct has been expanded to include fields for both JWS and JWE, which is good. However, to improve code readability and maintainability, consider adding comments to explain the purpose of each new field, especially for those that might not be immediately clear to other developers.

For example:

type Token struct {
    KeyID                string                    // Identifier for the key used to sign the token
    SignatureAlgorithm   jose.SignatureAlgorithm   // Algorithm used for signing (JWS)
    EncryptionKeyID      string                    // Identifier for the key used to encrypt the token
    KeyAlgorithm         jose.KeyAlgorithm         // Algorithm used for key encryption (JWE)
    ContentEncryption    jose.ContentEncryption    // Algorithm used for content encryption (JWE)
    CompressionAlgorithm jose.CompressionAlgorithm // Algorithm used for compression (JWE)
    // ... (rest of the fields)
}

Adding these comments will make the purpose of each field clearer and improve the overall documentation of the struct.


Line range hint 537-626: Improve error handling and type safety in helper functions

The helper functions are generally well-implemented, but there are a few areas where they could be improved:

  1. In newToken(), consider returning an error instead of a ValidationError when the number of headers is invalid. This would make the error handling more consistent with Go conventions.

  2. The pointer() function uses reflection, which can be slow and unsafe. Consider using generics (if using Go 1.18+) or creating type-specific functions for common types.

  3. In validateTokenTypeValue(), the type assertion could be combined with the initial check:

func validateTokenTypeValue(values []string, raw any) bool {
    typ, ok := raw.(string)
    if !ok {
        return false
    }
    for _, t := range values {
        if t == typ {
            return true
        }
    }
    return false
}
  1. The isUnsafeNoneMagicConstant() function could be simplified:
func isUnsafeNoneMagicConstant(k any) bool {
    switch key := k.(type) {
    case unsafeNoneMagicConstant:
        return true
    case jose.JSONWebKey, *jose.JSONWebKey:
        _, ok := key.Key.(unsafeNoneMagicConstant)
        return ok
    }
    return false
}

These changes would improve the performance, readability, and maintainability of these helper functions.

🛑 Comments failed to post (22)
token/jwt/claims_jarm_test.go (1)

28-34: 💡 Codebase verification

Inconsistent usage of claim constants found.

The consts package is still being utilized in multiple locations across the codebase. This inconsistency contradicts the recent changes and reintroduces external dependencies that were previously removed.

  • Numerous files reference consts.ClaimXYZ, indicating that the refactor is incomplete.

Please ensure all instances of consts.ClaimXYZ are replaced with the direct claim constants to maintain consistency and improve code maintainability.

🔗 Analysis chain

LGTM! Verify consistent usage of claim constants.

The changes improve code clarity by using direct claim constants and aligning with the standardized ClaimExpirationTime. This reduces external dependencies and enhances maintainability.

To ensure consistency across the codebase, please run the following script:

This script will help identify any inconsistencies in claim constant usage and ensure that the consts package has been fully removed from JWT-related code.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify consistent usage of claim constants and absence of 'consts' package

# Test 1: Check for any remaining usage of 'consts' package
echo "Checking for remaining 'consts' package usage:"
rg --type go 'consts\.Claim'

# Test 2: Verify consistent usage of claim constants
echo "Verifying consistent usage of claim constants:"
rg --type go 'Claim(Issuer|Audience|JWTID|IssuedAt|ExpirationTime)'

Length of output: 68439

token/jwt/date.go (2)

129-160: 🛠️ Refactor suggestion

Consider removing unnecessary case in UnmarshalJSON.

The UnmarshalJSON method for ClaimStrings is well-implemented and handles various input types flexibly. However, the case for []string might be unnecessary, as JSON unmarshaling typically produces []interface{} for arrays.

Consider removing the unnecessary case:

 switch v := value.(type) {
 case string:
     aud = append(aud, v)
-case []string:
-    aud = ClaimStrings(v)
 case []interface{}:
     for _, vv := range v {
         vs, ok := vv.(string)
         if !ok {
             return ErrInvalidType
         }
         aud = append(aud, vs)
     }
 case nil:
     return nil
 default:
     return ErrInvalidType
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
	var value interface{}

	if err = json.Unmarshal(data, &value); err != nil {
		return err
	}

	var aud []string

	switch v := value.(type) {
	case string:
		aud = append(aud, v)
	case []interface{}:
		for _, vv := range v {
			vs, ok := vv.(string)
			if !ok {
				return ErrInvalidType
			}
			aud = append(aud, vs)
		}
	case nil:
		return nil
	default:
		return ErrInvalidType
	}

	*s = aud

	return
}

31-46: 🛠️ Refactor suggestion

Consider simplifying the MarshalJSON method.

The current implementation is correct, but it can be simplified by using string concatenation instead of byte slice operations.

Here's a suggested improvement:

 func (date NumericDate) MarshalJSON() (b []byte, err error) {
     var prec int
     if TimePrecision < time.Second {
         prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
     }
     truncatedDate := date.UTC().Truncate(TimePrecision)
     seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
     nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)
-    output := append([]byte(seconds), []byte(nanosecondsOffset)[1:]...)
-    return output, nil
+    output := seconds + nanosecondsOffset[1:]
+    return []byte(output), nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func (date NumericDate) MarshalJSON() (b []byte, err error) {
	var prec int

	if TimePrecision < time.Second {
		prec = int(math.Log10(float64(time.Second) / float64(TimePrecision)))
	}

	truncatedDate := date.UTC().Truncate(TimePrecision)

	seconds := strconv.FormatInt(truncatedDate.Unix(), 10)
	nanosecondsOffset := strconv.FormatFloat(float64(truncatedDate.Nanosecond())/float64(time.Second), 'f', prec, 64)

	output := seconds + nanosecondsOffset[1:]
	return []byte(output), nil
}
token/jwt/claims_jarm.go (4)

33-55: ⚠️ Potential issue

LGTM! But there's a potential issue in GetSubject.

The new getter methods provide a consistent interface for accessing claims, which is great for usability and maintainability.

However, there's a potential issue in the GetSubject method:

func (c *JARMClaims) GetSubject() (sub string, err error) {
    return c.toString(ClaimIssuer)
}

It seems to be using ClaimIssuer instead of ClaimSubject. This should be corrected to:

func (c *JARMClaims) GetSubject() (sub string, err error) {
    return c.toString(ClaimSubject)
}

57-58: ⚠️ Potential issue

Implement validation logic in the Valid method.

The current implementation of the Valid method always returns nil, which means it's not performing any actual validation. This could lead to invalid claims being accepted.

Consider implementing actual validation logic. If this is intended to be a placeholder for future implementation, add a TODO comment to make it clear:

func (c *JARMClaims) Valid(opts ...ClaimValidationOption) (err error) {
    // TODO: Implement validation logic
    return nil
}

Alternatively, implement basic validation checks, such as verifying that required fields are not empty and that the token is not expired.


120-123: ⚠️ Potential issue

Consider handling errors from toNumericDate.

The update to use toNumericDate for IssuedAt and ExpirationTime is correct given the change to pointer types. However, the error return values from toNumericDate are currently being discarded.

Consider handling these errors to prevent silent failures. Here's a suggested improvement:

var err error
if c.IssuedAt, err = toNumericDate(v); err != nil {
    // Handle or log the error
}
if c.ExpirationTime, err = toNumericDate(v); err != nil {
    // Handle or log the error
}

This way, any issues during the conversion process can be properly addressed or logged.


25-31: 💡 Codebase verification

Potential Nil Dereference Issues with IssuedAt and ExpirationTime

The recent changes to IssuedAt and ExpirationTime fields as pointer types (*NumericDate) introduce potential risks. Multiple instances have been found where these fields are dereferenced without prior nil checks, which can lead to runtime panics if the fields are nil.

Affected Locations:

  • token/jwt/claims_id_token.go: return c.IssuedAt.UTC()
  • token/jwt/claims_id_token.go: ret[consts.ClaimIssuedAt] = c.IssuedAt.Unix()
  • handler/rfc7523/handler.go: issuedDate = claims.IssuedAt.Time()
  • handler/rfc7523/handler_test.go (multiple lines)
  • token/jwt/claims_jwt.go: if c.IssuedAt.IsZero()
  • token/jwt/claims_jwt.go: ret[ClaimIssuedAt] = c.IssuedAt.Unix()
  • handler/openid/strategy_jwt.go: if claims.ExpirationTime.Before(time.Now().UTC())

Recommendation:

  • Add nil checks before dereferencing IssuedAt and ExpirationTime in all affected locations to ensure safe access.
  • Review and update tests to handle nil scenarios appropriately.
🔗 Analysis chain

LGTM! Consider verifying usage across the codebase.

The change of IssuedAt and ExpirationTime to pointer types (*NumericDate) is a good improvement. It allows for more explicit handling of optional values. However, ensure that this change is reflected consistently across the codebase where these fields are used.

To verify the impact of this change, run the following script:


Further Verification Required: Ensure all usages handle pointer types appropriately.

To confirm that the changes to IssuedAt and ExpirationTime as pointer types (*NumericDate) are correctly handled throughout the codebase, please run the following scripts:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for usages of IssuedAt and ExpirationTime fields

# Search for direct accesses to these fields
echo "Checking direct field accesses:"
rg --type go -n "\.IssuedAt|\.ExpirationTime" --glob '!token/jwt/claims_jarm.go'

# Search for method calls that might be affected
echo "Checking potentially affected method calls:"
rg --type go -n "GetExpirationTime|GetIssuedAt" --glob '!token/jwt/claims_jarm.go'

Length of output: 7114


Script:

#!/bin/bash
# Description: Check for dereferences of IssuedAt and ExpirationTime without nil checks

# Search for dereferences without preceding nil checks
echo "Checking for dereferences without nil checks:"
rg --type go '\.IssuedAt\.' --glob '!token/jwt/claims_jarm.go' | rg -v 'if.*\.IssuedAt.*== nil'

rg --type go '\.ExpirationTime\.' --glob '!token/jwt/claims_jarm.go' | rg -v 'if.*\.ExpirationTime.*== nil'

Length of output: 1669


Script:

#!/bin/bash
# Description: Check for assignments to IssuedAt and ExpirationTime ensuring they are initialized as pointers

echo "Checking for assignments to IssuedAt:"
rg --type go 'claims\.IssuedAt\s*=' --glob '!token/jwt/claims_jarm.go'

echo "Checking for assignments to ExpirationTime:"
rg --type go 'claims\.ExpirationTime\s*=' --glob '!token/jwt/claims_jarm.go'

Length of output: 1139

token/jwt/claims_id_token.go (1)

173-263: ⚠️ Potential issue

LGTM: Comprehensive JSON unmarshaling with room for improvement

The UnmarshalJSON method provides a thorough implementation for decoding JSON data into the IDTokenClaims struct. It handles various claim types well and correctly populates the Extra field for additional claims.

Consider refactoring this method to improve readability and maintainability:

  1. Extract the claim handling logic into separate methods for each claim type.
  2. Use a map of claim handlers to reduce the size of the switch statement.

Example refactoring:

func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
	claims := MapClaims{}
	if err := jjson.NewDecoder(bytes.NewReader(data)).Decode(&claims); err != nil {
		return errorsx.WithStack(err)
	}

	handlers := map[string]func(any) error{
		ClaimJWTID:      c.handleStringClaim(&c.JTI),
		ClaimIssuer:     c.handleStringClaim(&c.Issuer),
		ClaimSubject:    c.handleStringClaim(&c.Subject),
		ClaimAudience:   c.handleAudienceClaim,
		ClaimNonce:      c.handleStringClaim(&c.Nonce),
		ClaimExpirationTime: c.handleNumericDateClaim(&c.ExpirationTime),
		// ... other claims ...
	}

	for claim, value := range claims {
		if handler, ok := handlers[claim]; ok {
			if err := handler(value); err != nil {
				return fmt.Errorf("claim %s could not be decoded", claim)
			}
		} else {
			c.handleExtraClaim(claim, value)
		}
	}

	return nil
}

func (c *IDTokenClaims) handleStringClaim(target *string) func(any) error {
	return func(value any) error {
		if s, ok := value.(string); ok {
			*target = s
			return nil
		}
		return fmt.Errorf("expected string value")
	}
}

// Implement other handler methods...

Additionally, there's a potential security issue in the error message on line 258. It includes the claim value, which could expose sensitive information. Apply this diff to address the issue:

-			return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
+			return fmt.Errorf("claim %s could not be decoded", claim)

These changes will improve the method's readability, maintainability, and security.

token/jwt/token_test.go (1)

420-420: 💡 Codebase verification

Incomplete Replacement of Valid() with IsSignatureValid()

Multiple instances of .Valid() are still present in the codebase. Please ensure all necessary instances have been updated to IsSignatureValid() to maintain consistency and improve token validation reliability.

🔗 Analysis chain

LGTM! Please clarify the implications of these changes.

The updates to use token.Claims.ToMapClaims() and token.IsSignatureValid() appear to be part of a broader refactoring of the JWT handling system. These changes likely improve type safety and provide more specific validity checks.

Could you please clarify:

  1. The implications of using ToMapClaims() instead of directly accessing Claims?
  2. The difference between IsSignatureValid() and the previous Valid() method?

To help verify the extent of these changes, please run the following script:

Also applies to: 429-429

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for other occurrences of ToMapClaims and IsSignatureValid

echo "Occurrences of ToMapClaims:"
rg --type go "ToMapClaims\(\)"

echo "Occurrences of IsSignatureValid:"
rg --type go "IsSignatureValid\(\)"

echo "Occurrences of Valid (possible candidates for replacement):"
rg --type go "\.Valid\(\)"

Length of output: 1734


Script:

#!/bin/bash
# Description: Re-run the search for ToMapClaims, IsSignatureValid, and Valid in Go files

echo "Occurrences of ToMapClaims():"
rg --type go "ToMapClaims\(\)"

echo "Occurrences of IsSignatureValid():"
rg --type go "IsSignatureValid\(\)"

echo "Occurrences of Valid():"
rg --type go "\.Valid\(\)"

Length of output: 4576

token/jwt/token.go (7)

192-244: 🛠️ Refactor suggestion

Consider adding error handling to AssignJWE()

The SetJWS(), SetJWE(), and AssignJWE() methods look good overall. However, AssignJWE() could benefit from some additional error handling or validation. For instance:

  1. Check if the required fields in the jwe.Header are present before accessing them.
  2. Return an error if any of the type assertions fail.

Here's a suggestion for improving AssignJWE():

func (t *Token) AssignJWE(jwe *jose.JSONWebEncryption) error {
    if jwe == nil {
        return errors.New("jwe is nil")
    }

    t.HeaderJWE = map[string]any{
        JSONWebTokenHeaderAlgorithm: jwe.Header.Algorithm,
    }

    if jwe.Header.KeyID != "" {
        t.HeaderJWE[JSONWebTokenHeaderKeyIdentifier] = jwe.Header.KeyID
        t.EncryptionKeyID = jwe.Header.KeyID
    }

    for header, value := range jwe.Header.ExtraHeaders {
        h := string(header)
        t.HeaderJWE[h] = value

        switch h {
        case JSONWebTokenHeaderEncryptionAlgorithm:
            if v, ok := value.(string); ok {
                t.ContentEncryption = jose.ContentEncryption(v)
            } else {
                return fmt.Errorf("invalid type for %s", h)
            }
        case JSONWebTokenHeaderCompressionAlgorithm:
            if v, ok := value.(string); ok {
                t.CompressionAlgorithm = jose.CompressionAlgorithm(v)
            } else {
                return fmt.Errorf("invalid type for %s", h)
            }
        }
    }

    t.KeyAlgorithm = jose.KeyAlgorithm(jwe.Header.Algorithm)
    return nil
}

This version adds error handling and returns an error if any issues are encountered during the assignment process.


247-309: ⚠️ Potential issue

Security consideration in CompactEncrypted()

The CompactEncrypted() and CompactSigned() methods are well-implemented overall. However, there's a potential security consideration in CompactEncrypted():

On line 272, the method is using the typ header from the JWS token as the cty (Content Type) header for the JWE token. This could potentially lead to information leakage or other security issues if the JWS typ header contains sensitive or unexpected values.

Consider either:

  1. Using a fixed value for the cty header instead of copying it from the JWS typ.
  2. Implementing a whitelist of allowed values for the cty header.

For example:

if _, ok := opts.ExtraHeaders[JSONWebTokenHeaderContentType]; !ok {
    opts.ExtraHeaders[JSONWebTokenHeaderContentType] = JSONWebTokenTypeJWT
}

This ensures that the cty header is always set to a known, safe value.


345-435: 🛠️ Refactor suggestion

Simplify and improve maintainability of Valid() method

The Valid() method is comprehensive in its checks, which is good. However, it could be simplified and made more maintainable:

  1. Consider breaking down the validation logic into smaller, focused functions. This would improve readability and make the code easier to maintain.

  2. Use a slice of validation functions instead of a long series of if statements. This would make it easier to add or remove validation checks in the future.

  3. Consolidate error handling to reduce repetition.

Here's a suggestion for refactoring:

func (t *Token) Valid(opts ...HeaderValidationOption) error {
    vopts := &HeaderValidationOptions{
        types: []string{JSONWebTokenTypeJWT},
    }
    for _, opt := range opts {
        opt(vopts)
    }

    validations := []func() error{
        t.validateSignature,
        t.validateEncryptionHeaders(vopts),
        t.validateType(vopts),
        t.validateAlgorithm(vopts),
        t.validateKeyID(vopts),
        t.validateKeyAlgorithm(vopts),
        t.validateContentEncryption(vopts),
        t.validateEncryptionKeyID(vopts),
    }

    var vErr ValidationError
    for _, validation := range validations {
        if err := validation(); err != nil {
            vErr.Errors |= err.(*ValidationError).Errors
            vErr.Inner = err
        }
    }

    if vErr.valid() {
        return nil
    }
    return &vErr
}

This approach makes the Valid() method more modular and easier to extend in the future.


438-467: 🛠️ Refactor suggestion

Simplify and improve robustness of IsJWTProfileAccessToken()

The IsJWTProfileAccessToken() method is functional, but it can be simplified and made more robust:

  1. Combine the checks for JSONWebTokenTypeAccessToken and JSONWebTokenTypeAccessTokenAlternative to reduce repetition.
  2. Use a helper function to check for valid token types.
  3. Simplify the logic flow to reduce nesting.

Here's a suggested refactoring:

func (t *Token) IsJWTProfileAccessToken() bool {
    isValidTokenType := func(tokenType string) bool {
        return tokenType == JSONWebTokenTypeAccessToken || tokenType == JSONWebTokenTypeAccessTokenAlternative
    }

    if t.HeaderJWE != nil {
        if cty, ok := t.HeaderJWE[JSONWebTokenHeaderContentType].(string); ok && isValidTokenType(cty) {
            return true
        }
    }

    if typ, ok := t.Header[JSONWebTokenHeaderType].(string); ok && isValidTokenType(typ) {
        return true
    }

    return false
}

This refactored version is more concise, easier to read, and less prone to errors. It also handles type assertions more safely.


468-513: 🛠️ Refactor suggestion

Enhance type safety with constants for predefined values

The HeaderValidationOption and HeaderValidationOptions types, along with their associated functions, provide a flexible way to configure header validation. To further improve type safety and reduce the likelihood of errors, consider using constants for predefined values.

For example:

const (
    DefaultJWTType = "JWT"
    // Add other common types here
)

func ValidateTypes(types ...string) HeaderValidationOption {
    return func(validator *HeaderValidationOptions) {
        if len(types) == 0 {
            validator.types = []string{DefaultJWTType}
        } else {
            validator.types = types
        }
    }
}

This approach has several benefits:

  1. It prevents typos in string literals.
  2. It makes it easier to change values across the codebase if needed.
  3. It provides better autocompletion in IDEs.

Consider applying this pattern to other validation options where appropriate.


515-535: ⚠️ Potential issue

Security consideration in unsignedToken()

The unsignedToken() function correctly creates an unsigned token. However, there's an important security consideration to keep in mind:

Unsigned tokens (with alg: none) are inherently insecure and should be used with extreme caution, if at all. They can lead to serious vulnerabilities if not handled properly in the verification process.

Consider adding a prominent comment warning about the security implications:

// unsignedToken creates an unsigned token (alg: none).
// WARNING: Unsigned tokens are inherently insecure and should only be used
// in controlled environments where the risks are well understood and mitigated.
// Never accept unsigned tokens from untrusted sources or use them for sensitive operations.
func unsignedToken(token *Token) (tokenString string, err error) {
    // ... (existing implementation)
}

Also, ensure that your token verification logic explicitly checks for and rejects unsigned tokens in production environments unless there's a very specific, controlled use case for them.


38-128: 🛠️ Refactor suggestion

Consider improving error handling in ParseCustomWithClaims()

The implementation of the parsing functions looks good overall. However, in ParseCustomWithClaims(), there's an opportunity to improve error handling:

  1. When creating a new token after a parsing error (line 60), consider returning a nil token instead of an empty one.
  2. In the claims validation section (lines 117-123), the error wrapping could be simplified.

Here's a suggested improvement for the claims validation error handling:

 if err = claims.Valid(); err != nil {
-    if e, ok := err.(*ValidationError); !ok {
-        err = &ValidationError{Inner: e, Errors: ValidationErrorClaimsInvalid}
+    if _, ok := err.(*ValidationError); !ok {
+        err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
     }
 
     return token, err
 }

This change ensures that the original error is always preserved in the Inner field of ValidationError.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func ParseCustomWithClaims(tokenString string, claims MapClaims, keyFunc Keyfunc, algs ...jose.SignatureAlgorithm) (token *Token, err error) {
	var parsed *jwt.JSONWebToken

	if parsed, err = jwt.ParseSigned(tokenString, algs); err != nil {
		return &Token{Claims: MapClaims(nil)}, &ValidationError{Errors: ValidationErrorMalformed, Inner: err}
	}

	// fill unverified claims
	// This conversion is required because go-jose supports
	// only marshalling structs or maps but not alias types from maps
	//
	// The KeyFunc(*Token) function requires the claims to be set into the
	// Token, that is an unverified token, therefore an UnsafeClaimsWithoutVerification is done first
	// then with the returned key, the claims gets verified.
	if err = parsed.UnsafeClaimsWithoutVerification(&claims); err != nil {
		return &Token{Claims: MapClaims(nil)}, &ValidationError{Errors: ValidationErrorClaimsInvalid, Inner: err}
	}

	// creates an unsafe token
	if token, err = newToken(parsed, claims); err != nil {
		return &Token{Claims: MapClaims(nil)}, err
	}

	if keyFunc == nil {
		return token, &ValidationError{Errors: ValidationErrorUnverifiable, text: "no Keyfunc was provided."}
	}

	var key any

	if key, err = keyFunc(token); err != nil {
		// keyFunc returned an error
		var ve *ValidationError

		if errors.As(err, &ve) {
			return token, ve
		}

		return token, &ValidationError{Errors: ValidationErrorUnverifiable, Inner: err}
	}

	if key == nil {
		return token, &ValidationError{Errors: ValidationErrorSignatureInvalid, text: "keyfunc returned a nil verification key"}
	}
	// To verify signature go-jose requires a pointer to
	// public key instead of the public key value.
	// The pointer values provides that pointer.
	// E.g. transform rsa.PublicKey -> *rsa.PublicKey
	key = pointer(key)

	// verify signature with returned key
	_, validNoneKey := key.(*unsafeNoneMagicConstant)
	isSignedToken := !(token.SignatureAlgorithm == SigningMethodNone && validNoneKey)
	if isSignedToken {
		if err = parsed.Claims(key, &claims); err != nil {
			return token, &ValidationError{Errors: ValidationErrorSignatureInvalid, text: err.Error()}
		}
	}

	// Validate claims
	// This validation is performed to be backwards compatible
	// with jwt-go library behavior
	if err = claims.Valid(); err != nil {
		if _, ok := err.(*ValidationError); !ok {
			err = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
		}

		return token, err
	}

	token.valid = true

	return token, nil
}
token/jwt/validate.go (3)

136-143: 🛠️ Refactor suggestion

Assess the necessity of constant-time string comparison in validString

Using subtle.ConstantTimeCompare for string comparison may not be necessary unless the comparison is sensitive to timing attacks. If not required, a standard string comparison can improve performance.

Apply this diff if constant-time comparison is unnecessary:

 func validString(value, cmp string, required bool) bool {
 	if value == "" {
 		return !required
 	}

-	return subtle.ConstantTimeCompare([]byte(value), []byte(cmp)) == 1
+	return value == cmp
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func validString(value, cmp string, required bool) bool {
	if value == "" {
		return !required
	}

	return value == cmp
}

146-164: 🛠️ Refactor suggestion

Refactor validDate to streamline error handling

Passing an error as a parameter to validDate is unconventional. Consider handling the error before invoking validDate and simplify the function signature accordingly.

Apply this diff to refactor the function:

-func validDate(valid validDateFunc, now int64, required bool, date *NumericDate, err error) bool {
-	if err != nil || valid == nil {
+func validDate(valid validDateFunc, now int64, required bool, date *NumericDate) bool {
+	if valid == nil {
 		return false
 	}

 	if date == nil {
 		if required {
 			return false
 		}

 		return true
 	}

 	if valid(date.Int64(), now, required) {
 		return true
 	}

 	return false
 }

Handle any errors related to date before calling validDate to maintain clear separation of concerns.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func validDate(valid validDateFunc, now int64, required bool, date *NumericDate) bool {
	if valid == nil {
		return false
	}

	if date == nil {
		if required {
			return false
		}

		return true
	}

	if valid(date.Int64(), now, required) {
		return true
	}

	return false
}

83-97: 🛠️ Refactor suggestion

Optimize verifyAudAny by reducing time complexity

The nested loops in verifyAudAny can be optimized to improve performance, especially with large audiences. Consider using a map to reduce the time complexity from O(n*m) to O(n+m).

Apply this diff to optimize the function:

 func verifyAudAny(aud []string, cmp []string, required bool) bool {
 	if len(aud) == 0 {
 		return !required
 	}

-	for _, c := range cmp {
-		for _, a := range aud {
-			if subtle.ConstantTimeCompare([]byte(a), []byte(c)) == 1 {
-				return true
-			}
-		}
-	}
+	audMap := make(map[string]struct{}, len(aud))
+	for _, a := range aud {
+		audMap[a] = struct{}{}
+	}
+	for _, c := range cmp {
+		if _, exists := audMap[c]; exists {
+			// Note: If constant-time comparison is required, retain it here.
+			return true
+		}
+	}

 	return false
 }

Please ensure that using a map does not compromise any security requirements related to constant-time comparisons.

Committable suggestion was skipped due to low confidence.

handler/rfc8693/refresh_token_type_handler.go (1)

152-152: 🛠️ Refactor suggestion

Consider refactoring token expiration logic into a helper function

The expression time.Now().UTC().Add(c.RefreshTokenLifespan).Truncate(jwt.TimePrecision) is used to set the token expiration time. If this pattern is used in multiple places across the codebase, extracting it into a helper function can improve maintainability and ensure consistent handling of token expiration times.

handler/openid/flow_hybrid_test.go (2)

192-194: ⚠️ Potential issue

Avoid using 'jwt.UnsafeParseSignedAny' without signature verification

Using jwt.UnsafeParseSignedAny skips signature verification of the JWT token, which can lead to security vulnerabilities by accepting tampered tokens. It's recommended to use a method that parses and verifies the token's signature to ensure its integrity.

Consider replacing with a secure parsing method that verifies the signature:

-	_, err := jwt.UnsafeParseSignedAny(idToken, claims)
+	token, err := jwt.ParseSigned(idToken)
+	if err != nil {
+		require.NoError(t, err)
+	}
+	err = token.Claims(publicKey, claims)
+	require.NoError(t, err)

Make sure to provide the correct publicKey for signature verification.

Also applies to: 337-339


432-433: ⚠️ Potential issue

Undefined variable 'key' in 'jwt.NewDefaultIssuerRS256Unverified'

The variable key used in jwt.NewDefaultIssuerRS256Unverified(key) is not defined within the scope of the makeOpenIDConnectHybridHandler function. This will result in a compilation error.

Apply this diff to define the key variable:

+	key := []byte("your-key-here") // Replace with the appropriate key material
	jwtStrategy := &jwt.DefaultStrategy{
		Config: config,
		Issuer: jwt.NewDefaultIssuerRS256Unverified(key),
	}

Ensure that key is properly initialized with the correct key material required for token signing.

Committable suggestion was skipped due to low confidence.

Copy link

@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: 24

🧹 Outside diff range and nitpick comments (17)
token/jwt/claims_jarm_test.go (1)

16-20: LGTM with a minor suggestion.

The changes look good and align with standard JWT claim names. The use of Now() and NewNumericDate() likely ensures consistent time handling across the codebase.

Consider extracting the duration time.Hour into a named constant for better readability and easier maintenance, especially if this duration is used in multiple places.

+const defaultTokenExpiration = time.Hour

var jarmClaims = &JARMClaims{
	Issuer:         "authelia",
	Audience:       []string{"tests"},
	JTI:            "abcdef",
	IssuedAt:       Now(),
-	ExpirationTime: NewNumericDate(time.Now().Add(time.Hour)),
+	ExpirationTime: NewNumericDate(time.Now().Add(defaultTokenExpiration)),
	// ... rest of the struct
}
handler/openid/validator_test.go (1)

38-40: Simplified ID token generation

The changes to the ID token generation function are appropriate and align well with the new strategy-based approach. The use of c.ToMapClaims() provides a more standardized way of handling claims.

Consider adding a comment explaining the purpose of this function for better code documentation.

 var genIDToken = func(c jwt.IDTokenClaims) string {
+	// Generate an ID token string from the given claims
 	s, _, err := j.Encode(context.TODO(), c.ToMapClaims())
 	require.NoError(t, err)
 	return s
 }
token/jwt/claims_jwt.go (3)

229-239: LGTM: Improved toTime function.

The new implementation of toTime is more robust, handling various input types through toInt64 and providing a boolean success indicator. This change improves error handling capabilities.

Minor suggestion: Consider using a named return for ok to make the function's intent clearer:

func toTime(v any, def time.Time) (t time.Time, ok bool) {
    // ... existing implementation ...
}

This change would make the function's signature more self-documenting.


241-268: LGTM: Well-implemented toInt64 function.

The toInt64 function is robust, handling various numeric types and json.Number. The error handling is thorough, and the use of a boolean return value is consistent with toTime.

Minor optimization suggestion: Consider using a type switch for cleaner code and potentially better performance:

func toInt64(v any) (val int64, ok bool) {
    switch t := v.(type) {
    case int64:
        return t, true
    case int32, int:
        return int64(t), true
    case float64:
        return int64(t), true
    case json.Number:
        if val, err := t.Int64(); err == nil {
            return val, true
        }
        if valf, err := t.Float64(); err == nil {
            return int64(valf), true
        }
    }
    return 0, false
}

This approach reduces the number of type assertions and may be more efficient.


270-304: LGTM: Well-implemented toNumericDate function.

The toNumericDate function is robust, handling various numeric types and json.Number. It consistently handles nil and zero values across different types, which is good for preventing unexpected behavior.

Suggestion for consistency: Consider refactoring the repeated code for handling zero values and creating NumericDate instances:

func toNumericDate(v any) (*NumericDate, error) {
    var value float64
    var ok bool

    switch val := v.(type) {
    case nil:
        return nil, nil
    case float64:
        value, ok = val, true
    case int64, int32, int:
        value, ok = float64(val), true
    case json.Number:
        value, _ = val.Float64()
        ok = true
    default:
        return nil, newError("value has invalid type", ErrInvalidType)
    }

    if !ok || value == 0 {
        return nil, nil
    }

    return newNumericDateFromSeconds(value), nil
}

This refactoring reduces code duplication and makes the function easier to maintain.

README.md (4)

Line range hint 25-106: Comprehensive improvements and new feature support

The README outlines numerous improvements and new features:

  1. Implementation of internal JWKS logic and JWE support for client authentication and issuance.
  2. Various fixes across different OAuth 2.0 flows (Refresh, PKCE, OpenID).
  3. Support for new RFCs:
    • RFC8628: OAuth 2.0 Device Authorization Grant
    • RFC8693: OAuth 2.0 Token Exchange
    • RFC9101: OAuth 2.0 JWT-Secured Authorization Requests

These enhancements significantly improve the library's functionality and standards compliance.

Consider updating or creating separate documentation for each major feature or RFC implementation. This would help users understand how to leverage these new capabilities effectively.

Would you like assistance in creating a documentation structure for these new features?


Line range hint 134-149: Appropriate acknowledgment and licensing clarification

The "Thanks" section properly acknowledges the original ORY Fosite project and clarifies the relationship between this fork and the original project. This transparency is commendable and aligns with open-source best practices.

Consider adding a link to the original ORY Fosite repository (https://github.com/ory/fosite) in the first paragraph of the "Thanks" section. This would make it easier for readers to reference the original project.

-This is a hard fork of [ORY Fosite](https://github.com/ory/fosite) under the
+This is a hard fork of [ORY Fosite](https://github.com/ory/fosite) (original repository [here](https://github.com/ory/fosite)) under the

107-107: Minor style correction needed

In American English, the abbreviation "etc" should be followed by a period. Please update the line as follows:

-- [x] Testing Package (mocks, etc)
++ [x] Testing Package (mocks, etc.)

This change will improve the document's consistency with standard American English style.

🧰 Tools
🪛 LanguageTool

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)


Remaining References to Deprecated Dependencies

The following dependencies listed as removed in the README are still referenced in the codebase:

  • go.opentelemetry.io/otel/trace in token/jwt/token.go

Please update or remove these references to align with the documented changes.

🔗 Analysis chain

Line range hint 1-24: Important changes to module path and Go version requirement

The README reflects two significant changes that may impact existing implementations:

  1. The module path has changed from github.com/ory/fosite to authelia.com/provider/oauth2.
  2. The minimum Go version requirement has been updated to 1.21.

To ensure these changes are consistently applied throughout the codebase, run the following script:

Please review the script output to ensure all necessary updates have been made.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify module path changes and Go version requirement

# Check for old module path
echo "Checking for old module path references:"
rg "github.com/ory/fosite" --type go

# Check go.mod for new module path and Go version
echo "Verifying go.mod contents:"
cat go.mod

# Check for Go version declarations in GitHub workflows
echo "Checking Go version in GitHub workflows:"
rg "go-version" .github/workflows/*.yml

Length of output: 8578

token/jwt/claims_map.go (2)

67-128: LGTM: Comprehensive audience claim handling with a minor suggestion

The new methods for handling audience claims are well-implemented and provide comprehensive functionality:

  1. Correct handling of audience claims as either a string or an array of strings
  2. Flexible verification options with VerifyAudienceAll and VerifyAudienceAny
  3. Consistent error handling and logic flow

These additions significantly enhance the JWT audience claim verification capabilities.

Consider adding a brief comment explaining the difference between VerifyAudience, VerifyAudienceAll, and VerifyAudienceAny for better clarity to users of this library.


131-200: LGTM: Well-implemented time-based claim handling with a minor suggestion

The new methods for handling time-based claims (exp, iat, nbf) are well-implemented:

  1. Correct handling of each time-based claim type
  2. Appropriate comparison logic in verification methods
  3. Consistent error handling and logic flow

These additions provide robust functionality for time-based JWT claim verification.

For consistency, consider using a common helper function for the time comparison logic in VerifyExpirationTime, VerifyIssuedAt, and VerifyNotBefore. This could reduce code duplication and make future maintenance easier.

token/jwt/token.go (1)

Line range hint 568-577: Consider using generics for the pointer function

The pointer function uses reflection to create a pointer to a value if it's not already a pointer. While this works, it could be simplified and made more type-safe using generics if you're using Go 1.18 or later.

Consider refactoring the pointer function to use generics:

func pointer[T any](v T) *T {
    return &v
}

This version is more type-safe and doesn't require reflection, which could improve performance.

token/jwt/validate.go (2)

118-125: Clarify the documentation for validInt64Future

The comment for validInt64Future mentions that it ensures the given value is in the future, but the implementation allows the value to be equal to now. If this behavior is intentional, consider updating the comment to accurately reflect the function's behavior.

Apply this change to clarify the comment:

-// validInt64Future ensures the given value is in the future.
+// validInt64Future ensures the given value is in the future or equal to the current time.

127-134: Align the documentation for validInt64Past

Similarly, the comment for validInt64Past should reflect that the value can be in the past or equal to now for consistency and clarity.

Apply this change to update the comment:

-// validInt64Past ensures the given value is in the past or the current value.
+// validInt64Past ensures the given value is in the past or equal to the current time.
handler/openid/strategy_jwt.go (3)

154-154: Consider making the 5-second time buffer configurable

The use of a 5-second buffer when validating auth_time accounts for minor time discrepancies. Making this buffer configurable can enhance flexibility for different deployment environments with varying time synchronization tolerances.


196-199: Properly handle errors during id_token_hint decoding

When decoding the id_token_hint, ensure that all potential errors are appropriately handled. Consider logging or differentiating errors to provide more context in failure scenarios.


217-221: Use jwt.Now() consistently for time calculations

To maintain consistency and ensure precise time claims, consider using jwt.Now() instead of time.Now() or time.Now().UTC() when working with JWT time calculations. For example, in lines 218 and 221, replace time.Now() and time.Now().UTC() with jwt.Now().

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1373d68 and 7fac454.

📒 Files selected for processing (42)
  • README.md (3 hunks)
  • handler/oauth2/flow_authorize_code_token.go (2 hunks)
  • handler/oauth2/flow_authorize_code_token_test.go (2 hunks)
  • handler/oauth2/flow_authorize_implicit.go (2 hunks)
  • handler/oauth2/flow_generic_code_token.go (2 hunks)
  • handler/oauth2/flow_refresh.go (2 hunks)
  • handler/oauth2/flow_refresh_test.go (11 hunks)
  • handler/oauth2/flow_resource_owner.go (2 hunks)
  • handler/oauth2/flow_resource_owner_test.go (2 hunks)
  • handler/oauth2/strategy_jwt_profile_test.go (8 hunks)
  • handler/openid/flow_device_authorization_test.go (4 hunks)
  • handler/openid/flow_hybrid.go (2 hunks)
  • handler/openid/flow_hybrid_test.go (4 hunks)
  • handler/openid/flow_refresh_token.go (3 hunks)
  • handler/openid/strategy_jwt.go (6 hunks)
  • handler/openid/strategy_jwt_test.go (13 hunks)
  • handler/openid/validator.go (3 hunks)
  • handler/openid/validator_test.go (15 hunks)
  • handler/rfc8628/device_authorize_handler.go (2 hunks)
  • handler/rfc8628/token_endpoint_handler_test.go (2 hunks)
  • handler/rfc8628/user_authorize_handler_test.go (5 hunks)
  • handler/rfc8693/access_token_type_handler.go (2 hunks)
  • handler/rfc8693/custom_jwt_type_handler.go (5 hunks)
  • handler/rfc8693/refresh_token_type_handler.go (2 hunks)
  • integration/oidc_explicit_test.go (6 hunks)
  • internal/test_helpers.go (2 hunks)
  • introspection_response_writer_test.go (1 hunks)
  • rfc8628_device_authorize_write_test.go (2 hunks)
  • testing/mock/client.go (1 hunks)
  • token/jarm/generate.go (2 hunks)
  • token/jwt/claims_id_token.go (7 hunks)
  • token/jwt/claims_id_token_test.go (3 hunks)
  • token/jwt/claims_jarm.go (2 hunks)
  • token/jwt/claims_jarm_test.go (2 hunks)
  • token/jwt/claims_jwt.go (5 hunks)
  • token/jwt/claims_jwt_test.go (5 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/claims_test.go (1 hunks)
  • token/jwt/date.go (1 hunks)
  • token/jwt/token.go (3 hunks)
  • token/jwt/token_test.go (8 hunks)
  • token/jwt/validate.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (28)
  • handler/oauth2/flow_authorize_code_token.go
  • handler/oauth2/flow_authorize_code_token_test.go
  • handler/oauth2/flow_authorize_implicit.go
  • handler/oauth2/flow_generic_code_token.go
  • handler/oauth2/flow_refresh.go
  • handler/oauth2/flow_refresh_test.go
  • handler/oauth2/flow_resource_owner.go
  • handler/oauth2/flow_resource_owner_test.go
  • handler/oauth2/strategy_jwt_profile_test.go
  • handler/openid/flow_device_authorization_test.go
  • handler/openid/flow_hybrid.go
  • handler/openid/flow_refresh_token.go
  • handler/openid/strategy_jwt_test.go
  • handler/openid/validator.go
  • handler/rfc8628/device_authorize_handler.go
  • handler/rfc8628/token_endpoint_handler_test.go
  • handler/rfc8628/user_authorize_handler_test.go
  • handler/rfc8693/access_token_type_handler.go
  • handler/rfc8693/custom_jwt_type_handler.go
  • handler/rfc8693/refresh_token_type_handler.go
  • integration/oidc_explicit_test.go
  • internal/test_helpers.go
  • introspection_response_writer_test.go
  • rfc8628_device_authorize_write_test.go
  • testing/mock/client.go
  • token/jwt/claims_id_token_test.go
  • token/jwt/claims_jwt_test.go
  • token/jwt/token_test.go
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~107-~107: In American English, abbreviations like “etc.” require a period.
Context: ...ssuance - [x] Testing Package (mocks, etc) - [ ] Clock Drift Support - [x] Ke...

(ETC_PERIOD)

🔇 Additional comments (66)
token/jwt/claims_test.go (3)

Line range hint 1-69: Overall improvements in JWT claims handling and testing.

The changes in this file contribute positively to the Authelia OAuth 2.0 Framework:

  1. Improved time precision handling in TestToTime.
  2. Comprehensive test coverage for the Filter function.

These modifications align well with the PR objectives of enhancing token handling and improving the testing framework.


30-69: Well-structured test function for Filter.

The new TestFilter function is well-implemented:

  • It uses table-driven tests, which is a good practice in Go.
  • The test cases cover important scenarios: no filtering, filtering everything, and partial filtering.
  • The function is easy to read and maintain.

Let's verify the Filter function implementation:

#!/bin/bash
# Description: Verify the implementation of the Filter function

# Test: Search for the Filter function definition
rg --type go -A 10 'func\s+Filter'

24-24: Improved time precision handling.

The change from Round(time.Second) to Truncate(TimePrecision) likely provides more precise time handling for JWT claims, which aligns with the overall improvements in token expiration handling mentioned in the PR summary.

Let's verify the TimePrecision constant:

✅ Verification successful

TimePrecision definition verified.

The TimePrecision variable is defined as time.Second in token/jwt/variables.go, confirming that the change to Truncate(TimePrecision) ensures precise time handling for JWT claims.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the definition of TimePrecision constant

# Test: Search for the TimePrecision constant definition
rg --type go -A 5 'const\s+TimePrecision'

Length of output: 1170


Script:

#!/bin/bash
# Description: Verify the definition of TimePrecision constant, variable, or type

# Search for constant, variable, or type definitions of TimePrecision
rg --type go -e 'const\s+TimePrecision' -e 'var\s+TimePrecision' -e 'type\s+TimePrecision'

Length of output: 92


Script:

#!/bin/bash
# Description: Search for all occurrences of TimePrecision in the codebase

rg 'TimePrecision' --type go

Length of output: 6001

token/jwt/claims_jarm_test.go (3)

43-43: LGTM!

The change from consts.ClaimJWTID to ClaimJWTID is consistent with the removal of the consts package dependency. The functionality of the test remains unchanged.


47-48: LGTM!

The changes are consistent with the updates made earlier in the file:

  1. Using ExpirationTime instead of ExpiresAt.
  2. Utilizing NewNumericDate() for time handling.

These modifications ensure consistency across the codebase and likely improve the precision of time-related operations in JWT claims.


49-50: LGTM!

The changes in this segment are consistent with the previous updates:

  1. Using ExpirationTime instead of ExpiresAt.
  2. Utilizing NewNumericDate() for time handling.

These modifications maintain consistency throughout the test function while preserving its original functionality.

token/jarm/generate.go (2)

22-22: Improved function signature clarity

The parameter name change from in to parameters enhances readability and self-documentation of the code. This is a good practice that makes the function's purpose clearer.


60-60: Efficient claims creation with jwt.NewJARMClaims

The refactoring to use jwt.NewJARMClaims is a significant improvement. It simplifies the claims creation process and reduces the potential for errors by handling the initialization of standard claims (JTI, IssuedAt, ExpiresAt) internally.

token/jwt/date.go (8)

1-11: LGTM: Package declaration and imports.

The package name jwt is appropriate for JWT-related functionality, and the imported packages are relevant to the implementation.


13-23: LGTM: NumericDate struct and related functions.

The implementation of NumericDate, Now(), and NewNumericDate() functions is correct and consistent with the use of TimePrecision.


31-46: LGTM: MarshalJSON method for NumericDate.

The implementation correctly formats the date as seconds since Unix epoch with fractional nanoseconds, consistently using TimePrecision for truncation. The manual construction of the JSON output is appropriate for this use case.


48-66: LGTM: UnmarshalJSON method for NumericDate.

The method correctly parses JSON numbers into NumericDate instances, with appropriate error handling.


68-76: LGTM: Int64 method for NumericDate.

The method correctly returns the time value as seconds since Unix epoch, handling nil pointers and consistently using TimePrecision for truncation.


78-127: LGTM: ClaimStrings type and validation methods.

The implementation of ClaimStrings and its validation methods (Valid, ValidAny, ValidAll) is correct and secure. The use of subtle.ConstantTimeCompare prevents timing attacks in claim validation.


162-168: LGTM: MarshalJSON method for ClaimStrings.

The method correctly handles both single string and array serialization based on the MarshalSingleStringAsArray flag.


25-29: ⚠️ Potential issue

Consider improving precision in newNumericDateFromSeconds().

The current implementation might lose precision when converting the fractional part of seconds to nanoseconds. Consider using math.Round() to minimize precision loss.

Apply this diff to improve precision:

 func newNumericDateFromSeconds(f float64) *NumericDate {
     round, frac := math.Modf(f)
-    return NewNumericDate(time.Unix(int64(round), int64(frac*1e9)))
+    return NewNumericDate(time.Unix(int64(round), int64(math.Round(frac*1e9))))
 }

Likely invalid or redundant comment.

token/jwt/claims_jarm.go (8)

10-21: LGTM! Well-implemented constructor function.

The NewJARMClaims function provides a clean and efficient way to create a new JARMClaims instance. It correctly initializes all required fields, including generating a unique JTI using UUID, and setting time-based fields using the provided lifespan.


24-31: LGTM! Improved struct definition with added flexibility.

The changes to the JARMClaims struct are well-thought-out:

  1. Using *NumericDate for IssuedAt and ExpirationTime allows for nil checks, improving robustness.
  2. The addition of the Extra field provides flexibility for storing non-standard claims.

These modifications enhance the struct's versatility and align well with the new constructor function.


62-96: LGTM! Improved ToMap method with better handling of field types.

The updates to the ToMap method are well-implemented:

  1. Proper handling of nil values for IssuedAt and ExpirationTime fields.
  2. Consistent representation of the audience field, using an empty slice when no audience is set.
  3. Correct mapping of all fields to their corresponding claim keys.

These changes improve the robustness and consistency of the method.


98-101: LGTM! Simple and effective ToMapClaims method.

The ToMapClaims method provides a convenient way to convert JARMClaims to MapClaims. Its implementation is straightforward and correct, leveraging the existing ToMap method.


130-133: LGTM! Simple and effective FromMapClaims method.

The FromMapClaims method provides a convenient way to populate JARMClaims from MapClaims. Its implementation is straightforward and correct, leveraging the existing FromMap method.


149-160: LGTM! Well-implemented toNumericDate helper method.

The toNumericDate method is a useful helper function for converting claim values to NumericDate. It correctly handles cases where the key is not present in the Extra map and delegates the actual conversion to the toNumericDate function. This implementation aligns well with the changes in the FromMap method and provides a consistent way to handle numeric date claims.


162-176: LGTM! Well-implemented toString helper method.

The toString method is a useful helper function for converting claim values to strings. It correctly handles cases where the key is not present in the Extra map and provides proper error handling when the value is not a string. This implementation enhances type safety and provides a consistent way to handle string claims.


179-181: LGTM! Good practice to include type assertion.

The addition of the type assertion var _ Claims = (*JARMClaims)(nil) is a great practice. It ensures at compile-time that JARMClaims correctly implements the Claims interface. This helps catch any interface implementation issues early in the development process.

handler/openid/validator_test.go (9)

24-29: Improved JWT handling with strategy-based approach

The refactoring of the DefaultStrategy struct to use a Strategy field with jwt.DefaultStrategy is a positive change. This approach allows for more flexible and customizable JWT handling, which can improve testability and make it easier to swap out JWT implementations if needed in the future.


62-63: Standardized time representation in tests

The use of jwt.Now() and jwt.NewNumericDate(...) for time-related fields is a good practice. It ensures consistency in time representation across the tests and aligns with JWT standards for handling time.


77-78: Consistent time representation

The standardization of time-related fields using jwt.Now() and jwt.NewNumericDate(...) is maintained here, which is good for consistency across test cases.


92-93: Maintained consistency in time representation

The continued use of jwt.Now() and jwt.NewNumericDate(...) for time-related fields maintains consistency across test cases, which is commendable.


106-107: Consistent RequestedAt field, AuthTime field missing

The use of jwt.Now() for the RequestedAt field maintains consistency with previous changes. However, I noticed that the AuthTime field is not present in this test case. Can you confirm if this is intentional for testing purposes?


119-120: Time fields swapped intentionally?

The use of JWT-specific time functions is maintained, which is good. However, I noticed that the time values for RequestedAt and AuthTime are swapped compared to previous test cases. Is this intentional to test a specific scenario where the auth time is more recent than the requested time?


133-134: Consistent time representation restored

The use of jwt.Now() for RequestedAt and jwt.NewNumericDate(time.Now().Add(-time.Minute)) for AuthTime is consistent with the standardized time representation used in earlier test cases. This maintains the correct order of AuthTime being in the past relative to RequestedAt.


147-148: Consistent time representation across all test cases

The changes in time-related fields across all remaining test cases consistently use jwt.Now(), jwt.NewNumericDate(), and relative time adjustments. This standardized approach to time representation significantly improves the consistency and readability of the entire test suite.

These changes will make it easier to maintain and update the tests in the future, as well as reduce the likelihood of time-related inconsistencies in test results.

Also applies to: 161-162, 175-176, 189-190, 203-204, 208-210, 222-223, 227-229, 240-243, 247-249


Line range hint 1-274: Summary of changes: Improved JWT handling and test consistency

The modifications in this file are part of a larger refactoring effort to standardize JWT handling and improve the flexibility of the OpenID Connect implementation. Key improvements include:

  1. Adoption of a strategy-based approach for JWT handling, allowing for more flexible and customizable implementations.
  2. Simplified ID token generation process.
  3. Standardized time representation across all test cases using JWT-specific time functions.

These changes enhance the maintainability, readability, and consistency of the test suite, which will likely lead to more robust and reliable tests in the future.

token/jwt/claims_jwt.go (2)

7-7: LGTM: Import of json package.

The addition of the json package is appropriate given the new functionality for handling json.Number types in the toInt64 function.


Line range hint 1-304: Overall assessment: Improved JWT claims handling

The changes in this file enhance the robustness and type-safety of JWT claims handling. Key improvements include:

  1. More comprehensive type handling in toTime, toInt64, and toNumericDate functions.
  2. Consistent error handling and success status reporting.
  3. Refactoring of claim constant references.

While the changes are generally good, consider addressing the following points:

  1. Fully handle the success status in the FromMap method for IssuedAt, NotBefore, and ExpiresAt.
  2. Implement the suggested optimizations for toInt64 and toNumericDate to improve code consistency and maintainability.

These changes will further enhance the quality and reliability of the JWT claims handling in this package.

README.md (1)

Line range hint 107-132: Significant changes in dependencies

The README lists several important changes to the project's dependencies:

  1. Removal of multiple dependencies, including go.opentelemetry.io/otel/trace and github.com/dgrijalva/jwt-go.
  2. Migration to newer versions of key dependencies:
    • github.com/go-jose/go-jose/v3 => github.com/go-jose/go-jose/v4
    • github.com/golang/mock => github.com/uber-go/mock
    • github.com/cristalhq/jwt/v4 => github.com/golang-jwt/jwt/v5

These changes likely improve the project's maintainability and security posture.

To ensure all dependency changes have been properly implemented, run the following script:

#!/bin/bash
# Verify dependency changes

# Check go.mod for removed dependencies
echo "Checking for removed dependencies:"
for dep in "go.opentelemetry.io/otel/trace" "github.com/ecordell/optgen" "github.com/asaskevich/govalidator" "github.com/gorilla/websocket" "github.com/magiconair/properties" "github.com/mattn/goveralls" "github.com/oleiade/reflections" "github.com/ory/go-acc" "github.com/ory/go-convenience" "github.com/ory/x" "github.com/gorilla/sessions" "github.com/gobuffalo/packr" "github.com/form3tech-oss/jwt-go" "github.com/dgrijalva/jwt-go" "github.com/golang-jwt/jwt"
do
    if grep -q "$dep" go.mod; then
        echo "Warning: $dep is still present in go.mod"
    fi
done

# Check for updated dependencies
echo "Checking for updated dependencies:"
grep -E "github.com/go-jose/go-jose/v4|github.com/uber-go/mock|github.com/golang-jwt/jwt/v5" go.mod

# Check for any remaining references to old dependencies in .go files
echo "Checking for references to old dependencies in .go files:"
rg --type go -l -e "go.opentelemetry.io/otel/trace" -e "github.com/ecordell/optgen" -e "github.com/asaskevich/govalidator" -e "github.com/gorilla/websocket" -e "github.com/magiconair/properties" -e "github.com/mattn/goveralls" -e "github.com/oleiade/reflections" -e "github.com/ory/go-acc" -e "github.com/ory/go-convenience" -e "github.com/ory/x" -e "github.com/gorilla/sessions" -e "github.com/gobuffalo/packr" -e "github.com/form3tech-oss/jwt-go" -e "github.com/dgrijalva/jwt-go" -e "github.com/golang-jwt/jwt"

Please review the script output to ensure all dependency changes have been correctly implemented across the codebase.

token/jwt/claims_map.go (3)

19-65: LGTM: Well-implemented issuer and subject claim handling

The new methods for retrieving and verifying issuer and subject claims are well-implemented. They follow good practices such as:

  1. Consistent error handling
  2. Proper handling of cases where the claim is not required
  3. Clear separation of concerns between retrieval and verification

These additions enhance the JWT claim handling capabilities of the library.


285-297: LGTM: Improved JSON unmarshaling with a request for clarification

The modification to the UnmarshalJSON method, using a custom JSON decoder with specific number type handling, appears to be a good improvement:

  1. It likely enhances the handling of numeric values in JWT claims.
  2. The use of jjson.UnmarshalIntOrFloat suggests more flexible number parsing.

Could you provide more context on the rationale behind this change? Specifically:

  1. What issues did the previous implementation have?
  2. How does this new approach resolve those issues?

This information would be valuable for understanding the full impact of this change and could be useful for documentation purposes.


Line range hint 1-360: Overall: Significant enhancements to JWT claim handling

This pull request introduces substantial improvements to the JWT claim handling capabilities:

  1. New methods for retrieving and validating various claim types (issuer, subject, audience, expiration time, issued at, not before).
  2. A comprehensive Valid method for validating multiple claims based on provided options.
  3. Enhanced JSON unmarshaling for better handling of numeric values.
  4. New helper methods for type-safe conversions and claim retrieval.

These changes significantly enhance the functionality, flexibility, and robustness of the JWT claim handling in this library. The code is well-structured, follows consistent patterns, and generally has good error handling.

While the changes are generally excellent, consider the suggestions provided in the individual comments for further improvements, particularly around error handling and code readability.

token/jwt/claims_id_token.go (6)

26-29: LGTM: Improved type handling for time-related fields

The change from time.Time to *NumericDate for ExpirationTime, IssuedAt, RequestedAt, and AuthTime fields is a good improvement. This allows for better JSON serialization/deserialization and explicit representation of null values.


62-139: LGTM: Comprehensive and flexible claim validation

The updated Valid method is a significant improvement:

  • It uses ClaimValidationOption for flexible validation configuration.
  • Comprehensive checks are performed on various claims (expiration, issuance, audience, etc.).
  • The method returns a ValidationError with detailed error information using efficient bitwise operations.

The implementation is thorough, well-structured, and easy to follow.


141-171: LGTM: Safe access methods for time-related fields

The addition of safe getter methods (GetExpirationTimeSafe, GetIssuedAtSafe, GetAuthTimeSafe, and GetRequestedAtSafe) is a good improvement:

  • They provide a safe way to access time-related fields without nil pointer dereferences.
  • Returning time.Unix(0, 0).UTC() for nil values is a reasonable default.

These methods enhance the usability and safety of the IDTokenClaims struct.


295-302: LGTM: Improved ToMap method

The updates to the ToMap method are well-implemented:

  • The method correctly handles the new *NumericDate types, converting them to Unix timestamps.
  • The use of constants from the consts package for claim keys improves consistency and maintainability.

These changes ensure that the ToMap method accurately represents the IDTokenClaims struct as a map.

Also applies to: 331-331


352-355: LGTM: New ToMapClaims method

The addition of the ToMapClaims method is a good improvement:

  • It provides a convenient way to get a MapClaims representation of the IDTokenClaims.
  • The implementation as a wrapper around ToMap is appropriate and efficient.

This method enhances compatibility with jwt-go library expectations.


371-381: LGTM: New toNumericDate method with a note

The addition of the toNumericDate method to the IDTokenClaims struct is a good improvement:

  • It provides a way to handle custom date claims stored in the Extra map.
  • The method correctly handles the case when the key is not present in the Extra map.

However, there's a potential issue to verify:

The method uses a toNumericDate function that is not defined in this file. Please ensure that this function is defined elsewhere in the package. You can verify this by running the following command:

If the function is not found, consider implementing it or updating the method to use a different approach for converting values to *NumericDate.

✅ Verification successful

Verification Successful: toNumericDate function is defined

The toNumericDate function is correctly defined in ./token/jwt/claims_jwt.go, ensuring that the toNumericDate method in claims_id_token.go references a valid function.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the toNumericDate function definition
rg --type go "func toNumericDate\(" .

Length of output: 124

token/jwt/token.go (13)

20-25: LGTM: New constructor function added

The New() function is a simple constructor for the Token struct. It initializes the Header and HeaderJWE maps, which is a good practice to avoid nil map errors.


28-36: LGTM: NewWithClaims function added

The NewWithClaims() function is a more comprehensive constructor that allows setting the signature algorithm and claims. It's a useful addition for creating tokens with predefined claims.


38-52: LGTM: Parsing functions added

The new parsing functions (Parse, ParseCustom, ParseWithClaims, ParseCustomWithClaims) provide a good range of options for parsing tokens. They handle different scenarios and allow for custom claims and algorithms.


130-147: LGTM: Token struct updated

The Token struct has been updated to include fields for both JWS (JSON Web Signature) and JWE (JSON Web Encryption) headers. This allows for more comprehensive token handling, supporting both signed and encrypted tokens.


192-210: LGTM: SetJWS and SetJWE methods added

The SetJWS and SetJWE methods provide a clean way to set the respective headers and properties for signed and encrypted tokens. This separation of concerns is good for maintainability.


212-244: LGTM: AssignJWE method added

The AssignJWE method correctly populates the token's JWE-related fields from a jose.JSONWebEncryption object. The nil check at the beginning is a good defensive programming practice.


247-295: LGTM: CompactEncrypted method added

The CompactEncrypted method provides a comprehensive way to create an encrypted and signed token. It handles both the signing and encryption processes, which is good for creating secure tokens.


297-342: LGTM: CompactSigned and CompactSignedString methods added

These methods provide ways to create signed tokens. The CompactSigned method returns both the token string and the signature, which can be useful for certain use cases. The CompactSignedString method is compatible with the jwt-go library, which is good for backwards compatibility.


438-467: LGTM: IsJWTProfileAccessToken method added

The IsJWTProfileAccessToken method correctly checks if the token is a JWT Profile Access Token by examining both the JWE and JWS headers. The type assertions and checks are thorough.


468-513: LGTM: Header validation options added

The HeaderValidationOption type and associated functions provide a flexible way to configure token validation. This is a good use of the functional options pattern.


515-535: LGTM: unsignedToken function added

The unsignedToken function correctly creates an unsigned token by setting the appropriate headers and encoding the token parts. The error handling is thorough.


537-566: LGTM: newToken function added

The newToken function creates a new Token struct from a parsed JWT and claims. It correctly handles the headers and performs necessary checks.


579-609: LGTM: Token type validation functions added

The validateTokenType and validateTokenTypeValue functions provide a way to validate the token type against a list of allowed values. The implementation is straightforward and correct.

token/jwt/validate.go (2)

8-19: Well-structured claim validation options

The ClaimValidationOptions struct and the associated option functions are well-designed, providing a flexible and extensible way to configure JWT claim validations.


69-81: Proper use of constant-time comparison in verifyAud

The verifyAud function correctly utilizes subtle.ConstantTimeCompare to prevent timing attacks when comparing audience strings.

handler/openid/strategy_jwt.go (6)

46-46: Update RequestedAt initialization to use jwt.Now()

The change from time.Now().UTC() to jwt.Now() ensures consistent time precision across JWT claims, which is beneficial for token validity and time comparison operations.


113-113: Embed jwt.Strategy instead of jwt.Signer in DefaultStrategy

By embedding jwt.Strategy, the code now accesses both encoding and decoding capabilities, consistent with the updates in token handling methods.


144-145: Initialize jwtClient for token operations

The creation of jwtClient using jwt.NewIDTokenClient(requester.GetClient()) appropriately encapsulates client information needed for ID Token encoding and decoding.


160-164: Validate required claims when max_age is specified

Ensuring AuthTime and RequestedAt are set when max_age is provided is critical for compliance with OpenID Connect specifications and for accurate token validation.


225-226: Initialize AuthTime using jwt.Now() when unset

Setting claims.AuthTime to jwt.Now() ensures that the auth_time claim accurately reflects the current time, which is important for token validation.


243-245: Encode ID Token using updated strategy

Using h.Strategy.Encode() with the provided parameters aligns with the embedding of jwt.Strategy and ensures correct token encoding with necessary headers and client information.

Comment on lines +28 to +34
ClaimIssuer: jwtClaims.Issuer,
ClaimAudience: jwtClaims.Audience,
ClaimJWTID: jwtClaims.JTI,
ClaimIssuedAt: jwtClaims.IssuedAt.Unix(),
ClaimExpirationTime: jwtClaims.ExpiresAt.Unix(),
"foo": jwtClaims.Extra["foo"],
"baz": jwtClaims.Extra["baz"],
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

LGTM, but there's a minor inconsistency.

The changes look good overall. The removal of the consts package dependency and the consistent use of ExpirationTime instead of ExpiresAt improve code clarity.

There's an inconsistency in the variable names. The jarmClaimsMap is using jwtClaims, which doesn't match the jarmClaims variable name used earlier in the file. This could lead to confusion or potential bugs.

Please update the variable name for consistency:

var jarmClaimsMap = map[string]any{
-	ClaimIssuer:         jwtClaims.Issuer,
-	ClaimAudience:       jwtClaims.Audience,
-	ClaimJWTID:          jwtClaims.JTI,
-	ClaimIssuedAt:       jwtClaims.IssuedAt.Unix(),
-	ClaimExpirationTime: jwtClaims.ExpiresAt.Unix(),
-	"foo":               jwtClaims.Extra["foo"],
-	"baz":               jwtClaims.Extra["baz"],
+	ClaimIssuer:         jarmClaims.Issuer,
+	ClaimAudience:       jarmClaims.Audience,
+	ClaimJWTID:          jarmClaims.JTI,
+	ClaimIssuedAt:       jarmClaims.IssuedAt.Unix(),
+	ClaimExpirationTime: jarmClaims.ExpirationTime.Unix(),
+	"foo":               jarmClaims.Extra["foo"],
+	"baz":               jarmClaims.Extra["baz"],
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ClaimIssuer: jwtClaims.Issuer,
ClaimAudience: jwtClaims.Audience,
ClaimJWTID: jwtClaims.JTI,
ClaimIssuedAt: jwtClaims.IssuedAt.Unix(),
ClaimExpirationTime: jwtClaims.ExpiresAt.Unix(),
"foo": jwtClaims.Extra["foo"],
"baz": jwtClaims.Extra["baz"],
ClaimIssuer: jarmClaims.Issuer,
ClaimAudience: jarmClaims.Audience,
ClaimJWTID: jarmClaims.JTI,
ClaimIssuedAt: jarmClaims.IssuedAt.Unix(),
ClaimExpirationTime: jarmClaims.ExpirationTime.Unix(),
"foo": jarmClaims.Extra["foo"],
"baz": jarmClaims.Extra["baz"],

Comment on lines +66 to +69
var strategy jwt.Strategy

if signer = config.GetJWTSecuredAuthorizeResponseModeSigner(ctx); signer == nil {
return "", "", errors.New("The JARM response modes require the JWTSecuredAuthorizeResponseModeSignerProvider to return a jwt.Signer but it didn't.")
if strategy = config.GetJWTSecuredAuthorizeResponseModeStrategy(ctx); strategy == nil {
return "", "", errors.New("The JARM response modes require the JWTSecuredAuthorizeResponseModeSignerProvider to return a jwt.Strategy but it didn't.")
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

Error Message Lacks Additional Context

The error message in token/jarm/generate.go does not include the original error returned by GetJWTSecuredAuthorizeResponseModeStrategy. Consider updating the error handling to include this context.

🔗 Analysis chain

Updated JWT handling with Strategy pattern

The transition from jwt.Signer to jwt.Strategy represents a shift towards a more flexible and extensible approach for JWT handling. This change allows for easier swapping of JWT encoding strategies and improves the overall design.

However, there are a couple of points to consider:

  1. Error handling: The error message has been updated to reflect the new jwt.Strategy usage, which is good. However, consider adding more context to the error message, such as including the actual error returned by GetJWTSecuredAuthorizeResponseModeStrategy.

  2. Encode method: The Encode method now includes additional parameters for headers and client. Ensure that this change is consistent with the jwt.Strategy interface definition and that all implementations of this interface have been updated accordingly.

To ensure consistency across the codebase, let's verify the jwt.Strategy interface and its implementations:

Also applies to: 72-72

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify jwt.Strategy interface and its implementations

# Test 1: Check the jwt.Strategy interface definition
echo "Checking jwt.Strategy interface definition:"
ast-grep --lang go --pattern 'type Strategy interface {
  $$$
  Encode($_, $_, $_) ($_, $_, $_)
  $$$
}'

# Test 2: Check for implementations of jwt.Strategy
echo "Checking for jwt.Strategy implementations:"
rg --type go "type .+ struct" -A 10 | rg "jwt\.Strategy"

# Test 3: Verify usage of strategy.Encode method
echo "Verifying usage of strategy.Encode method:"
rg --type go "strategy\.Encode\(ctx, .+, jwt\.With"

Length of output: 2848


Script:

#!/bin/bash
# Description: Verify enhanced error handling in token/jarm/generate.go

# Test 1: Extract the error message from lines 66-69
echo "Extracting error message from token/jarm/generate.go lines 66-69:"
sed -n '66,69p' token/jarm/generate.go

# Test 2: Check if the error message includes the original error
echo "Checking if the error message includes the original error:"
grep -E 'errors\.New\(.+GetJWTSecuredAuthorizeResponseModeStrategy\.Error\)' token/jarm/generate.go

Length of output: 678

Comment on lines +129 to +160
func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
var value interface{}

if err = json.Unmarshal(data, &value); err != nil {
return err
}

var aud []string

switch v := value.(type) {
case string:
aud = append(aud, v)
case []string:
aud = ClaimStrings(v)
case []interface{}:
for _, vv := range v {
vs, ok := vv.(string)
if !ok {
return ErrInvalidType
}
aud = append(aud, vs)
}
case nil:
return nil
default:
return ErrInvalidType
}

*s = aud

return
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider removing unreachable case in UnmarshalJSON.

The case []string: block might be unreachable because JSON unmarshaling into interface{} typically produces []interface{} for arrays, not []string. Consider removing this case to simplify the function.

Apply this diff to remove the unreachable case:

 switch v := value.(type) {
 case string:
     aud = append(aud, v)
-case []string:
-    aud = ClaimStrings(v)
 case []interface{}:
     for _, vv := range v {
         vs, ok := vv.(string)
         if !ok {
             return ErrInvalidType
         }
         aud = append(aud, vs)
     }
 case nil:
     return nil
 default:
     return ErrInvalidType
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
var value interface{}
if err = json.Unmarshal(data, &value); err != nil {
return err
}
var aud []string
switch v := value.(type) {
case string:
aud = append(aud, v)
case []string:
aud = ClaimStrings(v)
case []interface{}:
for _, vv := range v {
vs, ok := vv.(string)
if !ok {
return ErrInvalidType
}
aud = append(aud, vs)
}
case nil:
return nil
default:
return ErrInvalidType
}
*s = aud
return
}
func (s *ClaimStrings) UnmarshalJSON(data []byte) (err error) {
var value interface{}
if err = json.Unmarshal(data, &value); err != nil {
return err
}
var aud []string
switch v := value.(type) {
case string:
aud = append(aud, v)
case []interface{}:
for _, vv := range v {
vs, ok := vv.(string)
if !ok {
return ErrInvalidType
}
aud = append(aud, vs)
}
case nil:
return nil
default:
return ErrInvalidType
}
*s = aud
return
}

}

func (s ClaimStrings) MarshalJSON() (b []byte, err error) {
if len(s) == 1 && !MarshalSingleStringAsArray {
Copy link

Choose a reason for hiding this comment

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

💡 Codebase verification

MarshalSingleStringAsArray flag is not properly declared within the package.

  • The MarshalSingleStringAsArray flag is assigned in token/jwt/variables.go but lacks an explicit var or const declaration.
🔗 Analysis chain

Verify the MarshalSingleStringAsArray flag.

The MarshalSingleStringAsArray flag is used but not defined within this file. Ensure that this flag is declared and accessible within the package.

Run the following script to verify the flag's declaration:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the declaration of MarshalSingleStringAsArray flag.

# Test: Search for the flag declaration. Expect: One or more occurrences of the flag declaration.
rg --type go -g '!date.go' $'var MarshalSingleStringAsArray'

Length of output: 61


Script:

#!/bin/bash
# Description: Broadly verify the presence of MarshalSingleStringAsArray flag.

# Test: Search for any declaration or usage of MarshalSingleStringAsArray. Expect: One or more occurrences.
rg --type go -g '!token/jwt/date.go' 'MarshalSingleStringAsArray'

Length of output: 123


Script:

#!/bin/bash
# Description: Verify the declaration type of MarshalSingleStringAsArray flag.

# Test: Search for variable or constant declarations of MarshalSingleStringAsArray. Expect: One or more declarations.
rg --type go -g '!token/jwt/date.go' '^(var|const)\s+MarshalSingleStringAsArray\b'

Length of output: 84

Comment on lines +33 to +55
func (c *JARMClaims) GetExpirationTime() (exp *NumericDate, err error) {
return c.ExpirationTime, nil
}

func (c *JARMClaims) GetIssuedAt() (iat *NumericDate, err error) {
return c.IssuedAt, nil
}

func (c *JARMClaims) GetNotBefore() (nbf *NumericDate, err error) {
return c.toNumericDate(ClaimNotBefore)
}

func (c *JARMClaims) GetIssuer() (iss string, err error) {
return c.Issuer, nil
}

func (c *JARMClaims) GetSubject() (sub string, err error) {
return c.toString(ClaimIssuer)
}

func (c *JARMClaims) GetAudience() (aud ClaimStrings, err error) {
return c.Audience, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

LGTM for most methods, but there's an issue in GetSubject.

The new getter methods provide a consistent interface for accessing claims, which is great for usability and maintainability. However, there's an issue in the GetSubject method:

func (c *JARMClaims) GetSubject() (sub string, err error) {
    return c.toString(ClaimIssuer)
}

It's using ClaimIssuer instead of ClaimSubject. This should be corrected to:

func (c *JARMClaims) GetSubject() (sub string, err error) {
    return c.toString(ClaimSubject)
}

Please fix the GetSubject method to use the correct claim key.

The implementation of other getter methods looks good.

Comment on lines +209 to +211
if subHint, err = tokenHint.Claims.GetSubject(); subHint == "" || err != nil {
return "", errorsx.WithStack(oauth2.ErrServerError.WithDebug("Provided id token from 'id_token_hint' does not have a subject."))
} else if hintSub != claims.Subject {
} else if subHint != claims.Subject {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error handling when retrieving subject from id_token_hint

The condition subHint == "" || err != nil conflates an empty subHint with errors during retrieval. Consider handling these cases separately to provide more precise error messages and improve troubleshooting.

Comment on lines +430 to +434
jwtStrategy := &jwt.DefaultStrategy{
Config: config,
Issuer: jwt.NewDefaultIssuerRS256Unverified(key),
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Undefined variable key in jwtStrategy initialization

In line 432, the variable key is used in jwt.NewDefaultIssuerRS256Unverified(key), but key is not defined within the provided code. This will result in a compilation error.

Consider defining the key variable before using it. For example, you might initialize it as follows:

key := []byte("your-test-key") // Initialize or load your key appropriate for testing

Ensure that key is properly defined and matches the expected type for the Issuer function.

Comment on lines +192 to +194
claims := &jwt.IDTokenClaims{}

claims := &IDTokenClaims{}

_, _, err := parser.ParseUnverified(idToken, claims)
_, err := jwt.UnsafeParseSignedAny(idToken, claims)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use of jwt.UnsafeParseSignedAny may bypass signature verification

The function jwt.UnsafeParseSignedAny used in line 194 does not verify the signature of the ID token. This could lead to acceptance of invalid or tampered tokens during testing, potentially masking issues in token generation and validation logic.

Consider using a method that performs signature verification to ensure the integrity of the ID token. Replace jwt.UnsafeParseSignedAny with jwt.ParseSigned or another function that validates the token's signature:

token, err := jwt.ParseSigned(idToken)
require.NoError(t, err)
err = token.Claims(expectedSigningKey, claims)
require.NoError(t, err)

Ensure you provide the appropriate signing key for verification.

Comment on lines +337 to +339
claims := &jwt.IDTokenClaims{}

_, err := jwt.UnsafeParseSignedAny(idToken, claims)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use of jwt.UnsafeParseSignedAny may bypass signature verification

Similarly, in line 339, using jwt.UnsafeParseSignedAny skips signature verification for the ID token. This can result in tests passing with invalid or manipulated tokens.

Use a secure parsing method that includes signature verification. Replace jwt.UnsafeParseSignedAny with a function like jwt.ParseSigned:

token, err := jwt.ParseSigned(idToken)
require.NoError(t, err)
err = token.Claims(expectedSigningKey, claims)
require.NoError(t, err)

This ensures that only properly signed tokens are accepted in tests.

Comment on lines +430 to +440
jwtStrategy := &jwt.DefaultStrategy{
Config: config,
Issuer: jwt.NewDefaultIssuerRS256Unverified(key),
}

var idStrategy = &DefaultStrategy{
Signer: &jwt.DefaultSigner{
GetPrivateKey: func(_ context.Context) (any, error) {
return gen.MustRSAKey(), nil
},
},
Config: &oauth2.Config{
MinParameterEntropy: minParameterEntropy,
Strategy: &jwt.DefaultStrategy{
Config: config,
Issuer: jwt.MustGenDefaultIssuer(),
},
Config: config,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Inconsistent issuer initialization between jwtStrategy and idStrategy

There is a discrepancy in how the issuers are initialized for jwtStrategy and idStrategy:

  • In jwtStrategy (lines 430-433), the issuer is set using jwt.NewDefaultIssuerRS256Unverified(key) with an undefined key.
  • In idStrategy (lines 436-438), the issuer is set using jwt.MustGenDefaultIssuer().

This inconsistency could lead to unexpected behavior or test failures due to different signing keys or issuer configurations.

Consider standardizing the issuer initialization for both strategies to ensure consistent behavior. For example, you could use the same issuer function and key for both:

// Define the key once
key := []byte("your-test-key") // Ensure the key is properly defined

// Initialize jwtStrategy and idStrategy with the same issuer
issuer := jwt.NewDefaultIssuerRS256Unverified(key)

jwtStrategy := &jwt.DefaultStrategy{
    Config: config,
    Issuer: issuer,
}

idStrategy := &DefaultStrategy{
    Strategy: &jwt.DefaultStrategy{
        Config: config,
        Issuer: issuer,
    },
    Config: config,
}

Ensure that the key variable is defined and that both strategies use the same issuer for consistency.

Copy link

@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: 4

🧹 Outside diff range and nitpick comments (7)
token/jwt/util.go (7)

19-22: LGTM! Consider adding a comment about validation limitations.

The regular expressions for validating JWT structures look correct. However, it's worth noting that these are basic structural checks and don't validate the content or cryptographic integrity of the tokens.

Consider adding a comment to clarify the limitations of this validation:

var (
+	// Note: These regular expressions only validate the basic structure of JWTs.
+	// They do not check the content or cryptographic integrity of the tokens.
	reSignedJWT    = regexp.MustCompile(`^[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.([-_A-Za-z0-9]+)?$`)
	reEncryptedJWT = regexp.MustCompile(`^[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+\.[-_A-Za-z0-9]+$`)
)

34-71: LGTM! Consider clarifying the relationship between client secret and password-based algorithms.

These functions provide a clear way to categorize JWT algorithms, which is crucial for proper JWT handling. The implementation looks correct and covers the standard algorithms.

In the IsEncryptedJWTClientSecretAlg function, consider clarifying the relationship between client secret algorithms and password-based algorithms. You might want to add a comment explaining that password-based algorithms are a subset of client secret algorithms:

func IsEncryptedJWTClientSecretAlg(alg string) (csa bool) {
	switch a := jose.KeyAlgorithm(alg); a {
	case jose.A128KW, jose.A192KW, jose.A256KW, jose.DIRECT, jose.A128GCMKW, jose.A192GCMKW, jose.A256GCMKW:
		return true
	default:
+		// Password-based algorithms are a subset of client secret algorithms
		return IsEncryptedJWTPasswordBasedAlg(a)
	}
}

73-143: LGTM! Consider improving error messages for easier debugging.

The headerValidateJWS and headerValidateJWE functions perform thorough validation of the headers, checking for required fields and potential security issues. The inclusion of PBES2 count validation in the JWE header is a good security practice.

Consider making the error messages more specific to aid in debugging. For example:

func headerValidateJWS(headers []jose.Header) (kid, alg string, err error) {
	switch len(headers) {
	case 1:
		break
	case 0:
-		return "", "", fmt.Errorf("jws header is missing")
+		return "", "", fmt.Errorf("JWS header is missing")
	default:
-		return "", "", fmt.Errorf("jws header is malformed")
+		return "", "", fmt.Errorf("JWS header is malformed: expected 1 header, got %d", len(headers))
	}

	if headers[0].Algorithm == "" && headers[0].KeyID == "" {
-		return "", "", fmt.Errorf("jws header 'alg' and 'kid' values are missing or empty")
+		return "", "", fmt.Errorf("JWS header 'alg' and 'kid' values are both missing or empty")
	}

	if headers[0].JSONWebKey != nil {
-		return "", "", fmt.Errorf("jws header 'jwk' value is present but not supported")
+		return "", "", fmt.Errorf("JWS header 'jwk' value is present but not supported in this implementation")
	}

	return headers[0].KeyID, headers[0].Algorithm, nil
}

Similar improvements can be made to the headerValidateJWE function.


145-166: LGTM! Consider simplifying error handling.

The PrivateKey interface correctly defines the methods required for a private key. The JWKLookupError struct provides a way to handle errors specific to JWK lookup, which is good for error handling and debugging.

The use of a bitfield for errors might be overly complex for the current implementation. Consider simplifying it if you're not using multiple error flags simultaneously:

type JWKLookupError struct {
	Description string
-	Errors      uint32 // bitfield.  see JWKLookupError... constants
+	ErrorCode   string // Use a string identifier for the error type
}

func (e *JWKLookupError) Error() string {
-	return fmt.Sprintf("Error occurred retrieving the JSON Web Key. %s", e.Description)
+	return fmt.Sprintf("Error occurred retrieving the JSON Web Key (%s): %s", e.ErrorCode, e.Description)
}

This approach would be simpler and more readable, while still allowing for different error types.


363-393: LGTM! Consider adding explicit error handling.

The EncodeCompactSigned and EncodeNestedCompactEncrypted functions implement the correct logic for encoding JWTs. The handling of nil headers by creating empty headers is a good practice.

Consider adding explicit error handling for the SetJWS and SetJWE methods. While these methods might not return errors in the current implementation, adding error checks can make the code more robust and easier to maintain if these methods are updated in the future to return errors.

For example:

func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) {
	token := New()

	if headers == nil {
		headers = &Headers{}
	}

-	token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm))
+	if err := token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm)); err != nil {
+		return "", "", fmt.Errorf("failed to set JWS: %w", err)
+	}

	return token.CompactSigned(key)
}

func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) {
	token := New()

	if headers == nil {
		headers = &Headers{}
	}

	if headersJWE == nil {
		headersJWE = &Headers{}
	}

-	token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm))
-	token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE)
+	if err := token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm)); err != nil {
+		return "", "", fmt.Errorf("failed to set JWS: %w", err)
+	}
+	if err := token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE); err != nil {
+		return "", "", fmt.Errorf("failed to set JWE: %w", err)
+	}

	return token.CompactEncrypted(keySig, keyEnc)
}

This change would make the error handling more explicit and consistent throughout the functions.


395-435: LGTM! Consider optimizing the assign function.

The utility functions getJWTSignature, assign, and getPublicJWK implement their intended functionality correctly. The getJWTSignature function properly handles different JWT formats, and getPublicJWK correctly handles the case of client secret-based algorithms.

Consider optimizing the assign function. In Go 1.15 and later, you can use maps.Copy from the golang.org/x/exp/maps package for a more efficient and idiomatic way to merge maps. If you're using an earlier version of Go or prefer not to use experimental packages, you could still optimize the current implementation:

-func assign(a, b map[string]any) map[string]any {
+func assign(a, b map[string]any) {
 	for k, w := range b {
 		if _, ok := a[k]; ok {
 			continue
 		}
 		a[k] = w
 	}
-	return a
 }

This change removes the unnecessary return value, as the function modifies the map in-place. Usage would change from a = assign(a, b) to assign(a, b).

If you decide to use maps.Copy, the function could be replaced entirely:

import "golang.org/x/exp/maps"

func assign(a, b map[string]any) {
	maps.Copy(a, b)
}

This would be more efficient for large maps.


437-469: LGTM! Consider refining the error wrapping in newError.

The UnsafeParseSignedAny function is correctly marked as unsafe and intended for testing only. The newError function provides a flexible way to create formatted errors with multiple error wrapping.

The current implementation of newError uses fmt.Errorf with %w for wrapping multiple errors. However, this might lead to unexpected behavior as fmt.Errorf only wraps the first error when multiple %w verbs are used. Consider using errors.Join (introduced in Go 1.20) for a more predictable way of combining multiple errors:

import "errors"

func newError(message string, err error, more ...error) error {
	if message != "" {
		err = fmt.Errorf("%s: %w", message, err)
	}
	if len(more) > 0 {
		return errors.Join(err, errors.Join(more...))
	}
	return err
}

This implementation ensures that all provided errors are properly combined, and the resulting error will implement the Unwrap() []error method, allowing for better error inspection.

If you're using an earlier version of Go, you might want to implement a custom error type that can hold multiple errors, or use a third-party errors package that supports this functionality.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7fac454 and 6540167.

📒 Files selected for processing (3)
  • token/jwt/claims_id_token.go (5 hunks)
  • token/jwt/claims_id_token_test.go (3 hunks)
  • token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • token/jwt/claims_id_token_test.go
🔇 Additional comments (6)
token/jwt/claims_id_token.go (3)

26-29: LGTM: Improved claim type definitions

The changes to *NumericDate for time-related fields (ExpirationTime, IssuedAt, RequestedAt, AuthTime) align better with JWT standards. The Extra field type change to map[string]any allows for more flexible additional claims.

Also applies to: 35-35


Line range hint 246-335: LGTM: Improved ToMap implementation

The updates to the ToMap method are well-implemented:

  • It correctly handles the new field types, particularly the *NumericDate fields.
  • The use of constants from the consts package for claim keys improves maintainability.
  • Nil checks and appropriate handling of empty values ensure the method behaves correctly in all cases.

These changes make the method more robust and consistent with the updated IDTokenClaims struct.


337-340: LGTM: Useful addition of ToMapClaims

The ToMapClaims method is a valuable addition:

  • It provides a convenient way to convert IDTokenClaims to a MapClaims type.
  • This likely improves interoperability with other JWT libraries or systems expecting this format.
  • The implementation is simple and leverages the existing ToMap method, promoting code reuse.

This addition enhances the usability of the IDTokenClaims struct in various JWT-related scenarios.

token/jwt/util.go (3)

24-32: LGTM! Functions provide basic structural validation.

The IsSignedJWT and IsEncryptedJWT functions correctly use the defined regular expressions to check if a token string matches the basic structure of a signed or encrypted JWT. This can be useful for initial validation or routing logic.


1-469: Overall, the implementation is solid with room for minor improvements.

This file provides a comprehensive set of functions for JWT and JWK handling. The implementation follows good practices and includes thorough error checking. Here's a summary of the main points from the review:

  1. The regular expressions for JWT validation are correct but limited to structural checks.
  2. The algorithm categorization functions are well-implemented, with a suggestion to clarify the relationship between client secret and password-based algorithms.
  3. Header validation functions are thorough, with a suggestion to improve error messages for easier debugging.
  4. The JWK lookup and search functions are well-implemented, with a note about potential non-deterministic key selection.
  5. The key derivation process in NewClientSecretJWK is correct, but there's a suggestion to clarify the use of the bits variable.
  6. JWT encoding functions could benefit from more explicit error handling.
  7. Some utility functions, like assign, could be optimized for better performance.
  8. The error wrapping in newError could be improved using errors.Join for better error combining.

Implementing these suggestions would further enhance the code's clarity, efficiency, and robustness. Great job overall on this comprehensive JWT and JWK handling implementation!


168-242: LGTM! Be aware of potential non-deterministic key selection.

The FindClientPublicJWK and SearchJWKS functions implement the correct logic for finding and matching JWKs. The handling of cases where multiple keys match is good.

There's a potential issue with non-deterministic key selection when multiple keys match and 'strict' is false. This could lead to inconsistent behavior. Let's verify if this is a concern in the current implementation:

If these searches return results, we should consider adding a comment or adjusting the implementation to ensure deterministic key selection.

Comment on lines 38 to 61
func (c *IDTokenClaims) GetExpirationTime() (exp *NumericDate, err error) {
return c.ExpirationTime, nil
}

if c.Subject != "" {
ret[consts.ClaimSubject] = c.Subject
} else {
delete(ret, consts.ClaimSubject)
func (c *IDTokenClaims) GetIssuedAt() (iat *NumericDate, err error) {
return c.IssuedAt, nil
}

func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
return toNumericDate(ClaimNotBefore)
}

func (c *IDTokenClaims) GetIssuer() (iss string, err error) {
return c.Issuer, nil
}

func (c *IDTokenClaims) GetSubject() (sub string, err error) {
return c.Subject, nil
}

func (c *IDTokenClaims) GetAudience() (aud ClaimStrings, err error) {
return c.Audience, nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

LGTM: New getter methods improve claim access

The addition of getter methods for various claims is a good improvement. They provide a consistent interface for retrieving claim values and allow for proper error handling.

However, there's a potential issue in the GetNotBefore method:

The GetNotBefore method uses an undefined toNumericDate function. This might lead to a compilation error. Please ensure that this function is defined elsewhere in the package or consider implementing it if it's missing.

Comment on lines 173 to 249
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
claims := MapClaims{}

decoder := jjson.NewDecoder(bytes.NewReader(data))
decoder.SetNumberType(jjson.UnmarshalIntOrFloat)

if err := decoder.Decode(&claims); err != nil {
return errorsx.WithStack(err)
}

var (
ok bool
err error
)

for claim, value := range claims {
ok = false

switch claim {
case ClaimJWTID:
c.JTI, ok = value.(string)
case ClaimIssuer:
c.Issuer, ok = value.(string)
case ClaimSubject:
c.Subject, ok = value.(string)
case ClaimAudience:
c.Audience, ok = toStringSlice(value)
case ClaimNonce:
c.Nonce, ok = value.(string)
case ClaimExpirationTime:
if c.ExpirationTime, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimIssuedAt:
if c.IssuedAt, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimRequestedAt:
if c.RequestedAt, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimAuthenticationTime:
if c.AuthTime, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimAuthenticationContextClassReference:
c.AuthenticationContextClassReference, ok = value.(string)
case ClaimAuthenticationMethodsReference:
c.AuthenticationMethodsReferences, ok = toStringSlice(value)
case ClaimAccessTokenHash:
c.AccessTokenHash, ok = value.(string)
case ClaimCodeHash:
c.CodeHash, ok = value.(string)
case ClaimStateHash:
c.StateHash, ok = value.(string)
default:
if c.Extra == nil {
c.Extra = make(map[string]any)
}

c.Extra[claim] = value

continue
}

if !ok {
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
}
}

return nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

LGTM with a security concern: Comprehensive JSON unmarshaling

The UnmarshalJSON method provides a thorough implementation for decoding JSON data into the IDTokenClaims struct. It handles various claim types well and correctly populates the Extra field for additional claims.

However, there's a potential security issue:

On line 239, the error message includes the claim value, which could potentially expose sensitive information. Consider modifying the error message to exclude the value:

-			return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
+			return fmt.Errorf("claim %s could not be decoded", claim)

This change will prevent potential leakage of sensitive claim values in error messages.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
claims := MapClaims{}
decoder := jjson.NewDecoder(bytes.NewReader(data))
decoder.SetNumberType(jjson.UnmarshalIntOrFloat)
if err := decoder.Decode(&claims); err != nil {
return errorsx.WithStack(err)
}
var (
ok bool
err error
)
for claim, value := range claims {
ok = false
switch claim {
case ClaimJWTID:
c.JTI, ok = value.(string)
case ClaimIssuer:
c.Issuer, ok = value.(string)
case ClaimSubject:
c.Subject, ok = value.(string)
case ClaimAudience:
c.Audience, ok = toStringSlice(value)
case ClaimNonce:
c.Nonce, ok = value.(string)
case ClaimExpirationTime:
if c.ExpirationTime, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimIssuedAt:
if c.IssuedAt, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimRequestedAt:
if c.RequestedAt, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimAuthenticationTime:
if c.AuthTime, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimAuthenticationContextClassReference:
c.AuthenticationContextClassReference, ok = value.(string)
case ClaimAuthenticationMethodsReference:
c.AuthenticationMethodsReferences, ok = toStringSlice(value)
case ClaimAccessTokenHash:
c.AccessTokenHash, ok = value.(string)
case ClaimCodeHash:
c.CodeHash, ok = value.(string)
case ClaimStateHash:
c.StateHash, ok = value.(string)
default:
if c.Extra == nil {
c.Extra = make(map[string]any)
}
c.Extra[claim] = value
continue
}
if !ok {
return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
}
}
return nil
}
func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
claims := MapClaims{}
decoder := jjson.NewDecoder(bytes.NewReader(data))
decoder.SetNumberType(jjson.UnmarshalIntOrFloat)
if err := decoder.Decode(&claims); err != nil {
return errorsx.WithStack(err)
}
var (
ok bool
err error
)
for claim, value := range claims {
ok = false
switch claim {
case ClaimJWTID:
c.JTI, ok = value.(string)
case ClaimIssuer:
c.Issuer, ok = value.(string)
case ClaimSubject:
c.Subject, ok = value.(string)
case ClaimAudience:
c.Audience, ok = toStringSlice(value)
case ClaimNonce:
c.Nonce, ok = value.(string)
case ClaimExpirationTime:
if c.ExpirationTime, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimIssuedAt:
if c.IssuedAt, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimRequestedAt:
if c.RequestedAt, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimAuthenticationTime:
if c.AuthTime, err = toNumericDate(value); err == nil {
ok = true
}
case ClaimAuthenticationContextClassReference:
c.AuthenticationContextClassReference, ok = value.(string)
case ClaimAuthenticationMethodsReference:
c.AuthenticationMethodsReferences, ok = toStringSlice(value)
case ClaimAccessTokenHash:
c.AccessTokenHash, ok = value.(string)
case ClaimCodeHash:
c.CodeHash, ok = value.(string)
case ClaimStateHash:
c.StateHash, ok = value.(string)
default:
if c.Extra == nil {
c.Extra = make(map[string]any)
}
c.Extra[claim] = value
continue
}
if !ok {
return fmt.Errorf("claim %s could not be decoded", claim)
}
}
return nil
}

Copy link

@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: 6

🧹 Outside diff range and nitpick comments (1)
token/jwt/util.go (1)

19-71: LGTM! Consider adding documentation for exported functions.

The regular expressions and utility functions are well-implemented and provide necessary checks for JWT handling. However, it would be beneficial to add godoc comments for exported functions to improve code documentation.

Consider adding godoc comments for exported functions like IsSignedJWT, IsEncryptedJWT, IsSignedJWTClientSecretAlgStr, IsSignedJWTClientSecretAlg, IsEncryptedJWTClientSecretAlg, and IsEncryptedJWTPasswordBasedAlg.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6540167 and 3d978f4.

📒 Files selected for processing (3)
  • token/jwt/claims_id_token.go (4 hunks)
  • token/jwt/claims_id_token_test.go (3 hunks)
  • token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • token/jwt/claims_id_token_test.go
🔇 Additional comments (7)
token/jwt/claims_id_token.go (6)

24-35: LGTM: Improved IDTokenClaims struct

The changes to the IDTokenClaims struct are well-implemented:

  • Using *NumericDate for time-related fields allows for better handling of optional fields and aligns with JWT standards.
  • New fields have been added to support additional claims, improving the struct's flexibility.

These changes enhance the overall functionality and standards compliance of the IDTokenClaims struct.


38-60: LGTM: Well-implemented getter methods with a minor concern

The new getter methods are a great addition:

  • They provide a consistent interface for accessing claim values.
  • The use of *NumericDate aligns with the struct field types.
  • Safe getter methods handle nil values gracefully.

However, there's a potential issue in the GetNotBefore method:

func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
    return toNumericDate(ClaimNotBefore)
}

This method seems to be calling toNumericDate with a constant ClaimNotBefore instead of a field from the struct. This might not work as intended.

Could you please verify the implementation of the GetNotBefore method? It should probably return a field from the struct instead of calling toNumericDate.

Also applies to: 141-171


Line range hint 246-334: LGTM: Well-implemented updates to the ToMap method

The updates to the ToMap method are well-implemented:

  • The method correctly handles the new field types, including *NumericDate.
  • The use of the consts package for claim keys improves consistency and maintainability.
  • Nil values are handled appropriately by deleting the corresponding keys.

These changes ensure that the ToMap method accurately represents the IDTokenClaims struct as a map, which is crucial for serialization and interoperability.


337-340: LGTM: Useful addition of ToMapClaims method

The new ToMapClaims method is a valuable addition:

  • It provides a convenient way to get a jwt-go MapClaims representation of the IDTokenClaims.
  • This method enhances compatibility with the jwt-go library.
  • The implementation is simple and efficient, leveraging the existing ToMap method.

This addition improves the overall usability and interoperability of the IDTokenClaims struct.


356-398: LGTM: Useful helper functions

The new helper functions are well-implemented and improve the code's functionality:

  1. toNumericDate method:

    • Provides a convenient way to convert claim values to NumericDate.
    • Handles the case where the claim might not exist in the Extra map.
  2. toStringSlice function:

    • Handles various input types (nil, string, []string, []interface{}), making it versatile for different JSON representations.
    • Provides clear type checking and conversion.

These functions enhance the overall robustness and flexibility of the claims handling in the package.


173-244: ⚠️ Potential issue

Fix potential exposure of sensitive data in error messages

The UnmarshalJSON method provides a thorough implementation for decoding JSON data into the IDTokenClaims struct. It handles various claim types well and correctly populates the Extra field for additional claims.

However, there's a potential security issue:

On line 239, the error message includes the claim value, which could potentially expose sensitive information. Consider modifying the error message to exclude the value:

-           return fmt.Errorf("claim %s with value %v could not be decoded", claim, value)
+           return fmt.Errorf("claim %s could not be decoded", claim)

This change will prevent potential leakage of sensitive claim values in error messages.

Likely invalid or redundant comment.

token/jwt/util.go (1)

1-17: LGTM! Package declaration and imports are appropriate.

The package name "jwt" is suitable for the file's purpose, and the imports cover both standard library and necessary third-party packages for JWT handling.

Comment on lines +73 to +144
func headerValidateJWS(headers []jose.Header) (kid, alg string, err error) {
switch len(headers) {
case 1:
break
case 0:
return "", "", fmt.Errorf("jws header is missing")
default:
return "", "", fmt.Errorf("jws header is malformed")
}

if headers[0].Algorithm == "" && headers[0].KeyID == "" {
return "", "", fmt.Errorf("jws header 'alg' and 'kid' values are missing or empty")
}

if headers[0].JSONWebKey != nil {
return "", "", fmt.Errorf("jws header 'jwk' value is present but not supported")
}

return headers[0].KeyID, headers[0].Algorithm, nil
}

func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) {
if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) {
return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty")
}

if header.Algorithm == "" {
return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty")
}

var (
value any
ok bool
)

if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) {
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]; ok {
switch p2c := value.(type) {
case float64:
if p2c > 5000000 {
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': more than 5,000,000", int(p2c))
} else if p2c < 200000 {
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': less than 200,000", int(p2c))
}
default:
return "", "", "", "", fmt.Errorf("jwe header 'p2c' value has invalid type %T", p2c)
}
}
}

if value, ok = header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]; ok {
switch encv := value.(type) {
case string:
if encv != "" {
enc = encv

break
}

return "", "", "", "", fmt.Errorf("jwe header 'enc' value is empty")
default:
return "", "", "", "", fmt.Errorf("jwe header 'enc' value has invalid type %T", encv)
}
}

if value, ok = header.ExtraHeaders[JSONWebTokenHeaderContentType]; ok {
cty, _ = value.(string)
}

return header.KeyID, header.Algorithm, enc, cty, nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving error handling and consistency in header validation functions.

The headerValidateJWS and headerValidateJWE functions perform important checks, but there are a few areas for improvement:

  1. Error messages could be more consistent and informative.
  2. The headerValidateJWE function has nested if statements that could be simplified.
  3. Consider using custom error types for better error handling.

Here's a suggestion for improving the headerValidateJWE function:

func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) {
    if header.Algorithm == "" {
        return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty")
    }

    if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) {
        return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty")
    }

    if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) {
        if err := validatePBES2Count(header); err != nil {
            return "", "", "", "", err
        }
    }

    enc, err = validateEncryptionAlgorithm(header)
    if err != nil {
        return "", "", "", "", err
    }

    cty, _ = header.ExtraHeaders[JSONWebTokenHeaderContentType].(string)

    return header.KeyID, header.Algorithm, enc, cty, nil
}

func validatePBES2Count(header jose.Header) error {
    value, ok := header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]
    if !ok {
        return nil
    }

    p2c, ok := value.(float64)
    if !ok {
        return fmt.Errorf("jwe header 'p2c' value has invalid type %T", value)
    }

    if p2c < 200000 || p2c > 5000000 {
        return fmt.Errorf("jwe header 'p2c' has an invalid value '%d': must be between 200,000 and 5,000,000", int(p2c))
    }

    return nil
}

func validateEncryptionAlgorithm(header jose.Header) (string, error) {
    value, ok := header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]
    if !ok {
        return "", nil
    }

    enc, ok := value.(string)
    if !ok {
        return "", fmt.Errorf("jwe header 'enc' value has invalid type %T", value)
    }

    if enc == "" {
        return "", fmt.Errorf("jwe header 'enc' value is empty")
    }

    return enc, nil
}

This refactoring improves readability, error handling, and consistency in the headerValidateJWE function.

Comment on lines +145 to +243
// PrivateKey properly describes crypto.PrivateKey.
type PrivateKey interface {
Public() crypto.PublicKey
Equal(x crypto.PrivateKey) bool
}

const (
JWKLookupErrorClientNoJWKS uint32 = 1 << iota
)

type JWKLookupError struct {
Description string
Errors uint32 // bitfield. see JWKLookupError... constants
}

func (e *JWKLookupError) GetDescription() string {
return e.Description
}

func (e *JWKLookupError) Error() string {
return fmt.Sprintf("Error occurred retrieving the JSON Web Key. %s", e.Description)
}

// FindClientPublicJWK given a BaseClient, JWKSFetcherStrategy, and search parameters will return a *jose.JSONWebKey on
// a valid match. The *jose.JSONWebKey is guaranteed to match the alg and use values, and if strict is true it must
// match the kid value as well.
func FindClientPublicJWK(ctx context.Context, client BaseClient, fetcher JWKSFetcherStrategy, kid, alg, use string, strict bool) (key *jose.JSONWebKey, err error) {
var (
keys *jose.JSONWebKeySet
)

if keys = client.GetJSONWebKeys(); keys != nil {
return SearchJWKS(keys, kid, alg, use, strict)
}

if location := client.GetJSONWebKeysURI(); len(location) > 0 {
if keys, err = fetcher.Resolve(ctx, location, false); err != nil {
return nil, err
}

if key, err = SearchJWKS(keys, kid, alg, use, strict); err == nil {
return key, nil
}

if keys, err = fetcher.Resolve(ctx, location, true); err != nil {
return nil, err
}

return SearchJWKS(keys, kid, alg, use, strict)
}

return nil, &JWKLookupError{Description: "No JWKs have been registered for the client"}
}

func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (key *jose.JSONWebKey, err error) {
if len(jwks.Keys) == 0 {
return nil, &JWKLookupError{Description: "The retrieved JSON Web Key Set does not contain any key."}
}

var keys []jose.JSONWebKey

if kid == "" {
keys = jwks.Keys
} else {
keys = jwks.Key(kid)
}

if len(keys) == 0 {
return nil, &JWKLookupError{Description: fmt.Sprintf("The JSON Web Token uses signing key with kid '%s' which was not found", kid)}
}

var matched []jose.JSONWebKey

for _, k := range keys {
if k.Use != use {
continue
}

if k.Algorithm != alg {
continue
}

matched = append(matched, k)
}

switch len(matched) {
case 1:
return &matched[0], nil
case 0:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
default:
if strict {
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
}

return &matched[0], nil
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving error handling and code organization in JWK lookup functions.

The FindClientPublicJWK and SearchJWKS functions are well-implemented, but there are a few areas for improvement:

  1. Error handling could be more consistent by using the custom JWKLookupError type throughout.
  2. The SearchJWKS function could be simplified by using early returns.
  3. Consider adding more detailed comments explaining the logic, especially for edge cases.

Here's a suggestion for improving the SearchJWKS function:

func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (*jose.JSONWebKey, error) {
    if len(jwks.Keys) == 0 {
        return nil, &JWKLookupError{Description: "The retrieved JSON Web Key Set does not contain any key."}
    }

    keys := jwks.Keys
    if kid != "" {
        keys = jwks.Key(kid)
    }

    if len(keys) == 0 {
        return nil, &JWKLookupError{Description: fmt.Sprintf("The JSON Web Token uses signing key with kid '%s' which was not found", kid)}
    }

    var matched []jose.JSONWebKey
    for _, k := range keys {
        if k.Use == use && k.Algorithm == alg {
            matched = append(matched, k)
        }
    }

    switch len(matched) {
    case 0:
        return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
    case 1:
        return &matched[0], nil
    default:
        if strict {
            return nil, &JWKLookupError{Description: fmt.Sprintf("Multiple matching keys found for kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
        }
        return &matched[0], nil
    }
}

This refactoring simplifies the logic, improves readability, and uses consistent error handling with the JWKLookupError type.

Comment on lines 244 to 362
// NewClientSecretJWK returns a client secret based JWK from a client secret value.
//
// The symmetric encryption key is derived from the client_secret value by using the left-most bits of a truncated
// SHA-2 hash of the octets of the UTF-8 representation of the client_secret. For keys of 256 or fewer bits, SHA-256
// is used; for keys of 257-384 bits, SHA-384 is used; for keys of 385-512 bits, SHA-512 is used. The hash value MUST
// be truncated retaining the left-most bits to the appropriate bit length for the AES key wrapping or direct
// encryption algorithm used, for instance, truncating the SHA-256 hash to 128 bits for A128KW. If a symmetric key with
// greater than 512 bits is needed, a different method of deriving the key from the client_secret would have to be
// defined by an extension. Symmetric encryption MUST NOT be used by public (non-confidential) Clients because of
// their inability to keep secrets.
func NewClientSecretJWK(ctx context.Context, secret []byte, kid, alg, enc, use string) (jwk *jose.JSONWebKey, err error) {
if len(secret) == 0 {
return nil, &JWKLookupError{Description: "The client is not configured with a client secret that can be used for symmetric algorithms"}
}

switch use {
case JSONWebTokenUseSignature:
var (
hasher hash.Hash
)

switch jose.SignatureAlgorithm(alg) {
case jose.HS256:
hasher = sha256.New()
case jose.HS384:
hasher = sha512.New384()
case jose.HS512:
hasher = sha512.New()
default:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unsupported algorithm '%s'", alg)}
}

if _, err = hasher.Write(secret); err != nil {
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())}
}

return &jose.JSONWebKey{
Key: hasher.Sum(nil),
KeyID: kid,
Algorithm: alg,
Use: use,
}, nil
case JSONWebTokenUseEncryption:
var (
hasher hash.Hash
bits int
)

keyAlg := jose.KeyAlgorithm(alg)

switch keyAlg {
case jose.A128KW, jose.A128GCMKW, jose.A192KW, jose.A192GCMKW, jose.A256KW, jose.A256GCMKW, jose.PBES2_HS256_A128KW:
hasher = sha256.New()
case jose.PBES2_HS384_A192KW:
hasher = sha512.New384()
case jose.PBES2_HS512_A256KW, jose.DIRECT:
hasher = sha512.New()
default:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unsupported algorithm '%s'", alg)}
}

switch keyAlg {
case jose.A128KW, jose.A128GCMKW, jose.PBES2_HS256_A128KW:
bits = aes.BlockSize
case jose.A192KW, jose.A192GCMKW, jose.PBES2_HS384_A192KW:
bits = aes.BlockSize * 1.5
case jose.A256KW, jose.A256GCMKW, jose.PBES2_HS512_A256KW:
bits = aes.BlockSize * 2
case jose.DIRECT:
switch jose.ContentEncryption(enc) {
case jose.A128CBC_HS256, "":
bits = aes.BlockSize * 2
case jose.A192CBC_HS384:
bits = aes.BlockSize * 3
case jose.A256CBC_HS512:
bits = aes.BlockSize * 4
default:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unsupported content encryption for the direct key algorthm '%s'", enc)}
}
}

if _, err = hasher.Write(secret); err != nil {
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())}
}

return &jose.JSONWebKey{
Key: hasher.Sum(nil)[:bits],
KeyID: kid,
Algorithm: alg,
Use: use,
}, nil
default:
return &jose.JSONWebKey{
Key: secret,
KeyID: kid,
Algorithm: alg,
Use: use,
}, nil
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider refactoring key derivation logic for better organization and error handling.

The NewClientSecretJWKFromClient and NewClientSecretJWK functions implement correct key derivation logic, but there are opportunities for improvement:

  1. The NewClientSecretJWK function is quite long and could be split into smaller, more focused functions.
  2. Error handling could be more consistent by using custom error types throughout.
  3. Consider using constants for magic numbers and repeated strings.

Here's a suggestion for refactoring the NewClientSecretJWK function:

func NewClientSecretJWK(ctx context.Context, secret []byte, kid, alg, enc, use string) (*jose.JSONWebKey, error) {
    if len(secret) == 0 {
        return nil, &JWKLookupError{Description: "The client is not configured with a client secret that can be used for symmetric algorithms"}
    }

    switch use {
    case JSONWebTokenUseSignature:
        return newSignatureJWK(secret, kid, alg)
    case JSONWebTokenUseEncryption:
        return newEncryptionJWK(secret, kid, alg, enc)
    default:
        return &jose.JSONWebKey{
            Key:       secret,
            KeyID:     kid,
            Algorithm: alg,
            Use:       use,
        }, nil
    }
}

func newSignatureJWK(secret []byte, kid, alg string) (*jose.JSONWebKey, error) {
    hasher, err := getHasher(jose.SignatureAlgorithm(alg))
    if err != nil {
        return nil, err
    }

    if _, err := hasher.Write(secret); err != nil {
        return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())}
    }

    return &jose.JSONWebKey{
        Key:       hasher.Sum(nil),
        KeyID:     kid,
        Algorithm: alg,
        Use:       JSONWebTokenUseSignature,
    }, nil
}

func newEncryptionJWK(secret []byte, kid, alg, enc string) (*jose.JSONWebKey, error) {
    keyAlg := jose.KeyAlgorithm(alg)
    hasher, err := getHasher(keyAlg)
    if err != nil {
        return nil, err
    }

    bits, err := getKeyBits(keyAlg, enc)
    if err != nil {
        return nil, err
    }

    if _, err := hasher.Write(secret); err != nil {
        return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to derive key from hashing the client secret. %s", err.Error())}
    }

    return &jose.JSONWebKey{
        Key:       hasher.Sum(nil)[:bits],
        KeyID:     kid,
        Algorithm: alg,
        Use:       JSONWebTokenUseEncryption,
    }, nil
}

// Implement getHasher and getKeyBits functions...

This refactoring improves code organization, readability, and maintainability by splitting the large function into smaller, more focused functions.

Comment on lines +395 to +470
func getJWTSignature(tokenString string) (signature string, err error) {
switch segments := strings.SplitN(tokenString, ".", 5); len(segments) {
case 5:
return "", errors.WithStack(errors.New("invalid token: the token is probably encrypted"))
case 3:
return segments[2], nil
default:
return "", errors.WithStack(fmt.Errorf("invalid token: the format is unknown"))
}
}

func assign(a, b map[string]any) map[string]any {
for k, w := range b {
if _, ok := a[k]; ok {
continue
}
a[k] = w
}
return a
}

func getPublicJWK(jwk *jose.JSONWebKey) jose.JSONWebKey {
if jwk == nil {
return jose.JSONWebKey{}
}

if _, ok := jwk.Key.([]byte); ok && IsSignedJWTClientSecretAlgStr(jwk.Algorithm) {
return jose.JSONWebKey{
KeyID: jwk.KeyID,
Key: jwk.Key,
Algorithm: jwk.Algorithm,
Use: jwk.Use,
Certificates: jwk.Certificates,
CertificatesURL: jwk.CertificatesURL,
CertificateThumbprintSHA1: jwk.CertificateThumbprintSHA1,
CertificateThumbprintSHA256: jwk.CertificateThumbprintSHA256,
}
}

return jwk.Public()
}

// UnsafeParseSignedAny is a function that will attempt to parse any signed token without any verification process.
// It's unsafe for production and should only be used for tests.
func UnsafeParseSignedAny(tokenString string, dest any) (token *jwt.JSONWebToken, err error) {
if token, err = jwt.ParseSigned(tokenString, SignatureAlgorithmsNone); err != nil {
return nil, err
}

if err = token.UnsafeClaimsWithoutVerification(dest); err != nil {
return nil, err
}

return token, nil
}

func newError(message string, err error, more ...error) error {
var format string
var args []any
if message != "" {
format = "%w: %s"
args = []any{err, message}
} else {
format = "%w"
args = []any{err}
}

for _, e := range more {
format += ": %w"
args = append(args, e)
}

err = fmt.Errorf(format, args...)
return err
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider improving error handling and adding documentation for utility functions.

The utility functions are well-implemented, but there are a few areas for improvement:

  1. Adding godoc comments for exported functions would improve the package documentation.
  2. The UnsafeParseSignedAny function should have a more prominent warning about its unsafe nature.
  3. Consider using custom error types for better error handling in some functions.

Here are some suggestions for improvement:

  1. Add godoc comments to exported functions, especially UnsafeParseSignedAny:
// UnsafeParseSignedAny attempts to parse any signed token without any verification process.
// WARNING: This function is unsafe for production use and should only be used for testing purposes.
// It may lead to security vulnerabilities if used with untrusted input.
func UnsafeParseSignedAny(tokenString string, dest any) (*jwt.JSONWebToken, error) {
    // ... (existing implementation)
}
  1. Consider using a custom error type for getJWTSignature:
type JWTError struct {
    Message string
}

func (e *JWTError) Error() string {
    return e.Message
}

func getJWTSignature(tokenString string) (signature string, err error) {
    segments := strings.SplitN(tokenString, ".", 5)
    switch len(segments) {
    case 5:
        return "", &JWTError{"invalid token: the token is probably encrypted"}
    case 3:
        return segments[2], nil
    default:
        return "", &JWTError{"invalid token: the format is unknown"}
    }
}
  1. The newError function could be simplified using errors.Join from the standard library (Go 1.20+):
func newError(message string, err error, more ...error) error {
    errors := make([]error, 0, len(more)+2)
    errors = append(errors, err)
    if message != "" {
        errors = append(errors, fmt.Errorf(message))
    }
    errors = append(errors, more...)
    return errors.Join(errors...)
}

These changes would improve the overall quality and maintainability of the utility functions.

Copy link

@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

🧹 Outside diff range and nitpick comments (8)
token/jwt/claims_id_token.go (3)

39-61: LGTM: New getter methods improve claim access

The addition of getter methods for various claims is a good improvement. They provide a consistent interface for retrieving claim values and allow for proper error handling.

However, there's a potential issue in the GetNotBefore method:

func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
	return toNumericDate(ClaimNotBefore)
}

The toNumericDate function is called with ClaimNotBefore, which seems to be a constant string. This might not be the intended behavior. Consider updating this method to return c.NotBefore instead, similar to other getter methods.


63-140: LGTM: Comprehensive claim validation implementation

The Valid method provides thorough validation of claims, covering expiration, issuance, audience, and other important checks. The use of a ValidationError struct for accumulating errors is a good practice.

Consider refactoring this method to improve readability:

  1. Extract the validation logic for each claim type into separate methods.
  2. Use a slice of validation functions to iterate over, reducing the method's complexity.

Example refactoring:

func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) error {
    vopts := &ClaimValidationOptions{}
    for _, opt := range opts {
        opt(vopts)
    }

    now := getValidationTime(vopts)
    vErr := new(ValidationError)

    validations := []func(*ValidationError, int64, *ClaimValidationOptions){
        c.validateExpiration,
        c.validateIssuedAt,
        c.validateNotBefore,
        c.validateIssuer,
        c.validateSubject,
        c.validateAudience,
    }

    for _, validate := range validations {
        validate(vErr, now, vopts)
    }

    if vErr.valid() {
        return nil
    }

    return vErr
}

func (c IDTokenClaims) validateExpiration(vErr *ValidationError, now int64, vopts *ClaimValidationOptions) {
    if date, err := c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
        vErr.Inner = errors.New("Token is expired")
        vErr.Errors |= ValidationErrorExpired
    }
}

// Implement other validation methods similarly...

This refactoring would make the Valid method more modular and easier to maintain.


174-251: LGTM with a suggestion: Comprehensive JSON marshaling and unmarshaling

The MarshalJSON and UnmarshalJSON methods provide thorough implementations for encoding and decoding JSON data. The UnmarshalJSON method handles various claim types well and correctly populates the Extra field for additional claims.

Consider refactoring the UnmarshalJSON method to improve readability:

  1. Extract the switch statement into a separate method.
  2. Use a map of claim handlers to reduce the size of the switch statement.

Example:

func (c *IDTokenClaims) UnmarshalJSON(data []byte) error {
    // ... (existing code)

    handlers := map[string]func(any) error{
        ClaimJWTID: func(v any) error {
            var ok bool
            c.JTI, ok = v.(string)
            return boolToError(ok)
        },
        // ... (other handlers)
    }

    for claim, value := range claims {
        if handler, ok := handlers[claim]; ok {
            if err := handler(value); err != nil {
                return fmt.Errorf("claim %s could not be decoded", claim)
            }
        } else {
            if c.Extra == nil {
                c.Extra = make(map[string]any)
            }
            c.Extra[claim] = value
        }
    }

    return nil
}

func boolToError(ok bool) error {
    if !ok {
        return errors.New("invalid type")
    }
    return nil
}

This refactoring would make the UnmarshalJSON method more modular and easier to maintain.

token/jwt/util.go (5)

73-143: LGTM: Header validation functions are thorough and well-implemented.

The headerValidateJWS and headerValidateJWE functions perform comprehensive checks on JWT headers. The error messages are descriptive, which will aid in debugging.

Consider extracting the PBES2 count validation in headerValidateJWE into a separate function for better readability and maintainability. For example:

func validatePBES2Count(header jose.Header) error {
    value, ok := header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]
    if !ok {
        return nil
    }

    p2c, ok := value.(float64)
    if !ok {
        return fmt.Errorf("jwe header 'p2c' value has invalid type %T", value)
    }

    if p2c < 200000 || p2c > 5000000 {
        return fmt.Errorf("jwe header 'p2c' has an invalid value '%d': must be between 200,000 and 5,000,000", int(p2c))
    }

    return nil
}

This would simplify the headerValidateJWE function and make the code more modular.


145-166: LGTM: PrivateKey interface and JWKLookupError type are well-defined.

The PrivateKey interface and JWKLookupError type provide good abstractions for handling private keys and JWK lookup errors respectively.

Consider adding comments to explain the purpose of the JWKLookupErrorClientNoJWKS constant and how the Errors bitfield in JWKLookupError should be used. This would improve the clarity of the code for future maintainers. For example:

// JWKLookupErrorClientNoJWKS indicates that the client has no JWKS configured
const (
    JWKLookupErrorClientNoJWKS uint32 = 1 << iota
    // Add more error constants here as needed
)

// JWKLookupError represents an error that occurred during JWK lookup
type JWKLookupError struct {
    Description string
    Errors      uint32 // Bitfield of error conditions. Use JWKLookupError... constants
}

168-242: LGTM: FindClientPublicJWK and SearchJWKS functions are well-implemented.

The FindClientPublicJWK and SearchJWKS functions provide robust functionality for retrieving and searching JWKs. The error handling is consistent, and the logic covers various scenarios.

Consider refactoring the SearchJWKS function to improve readability and reduce nesting. Here's a suggestion:

func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (*jose.JSONWebKey, error) {
    if len(jwks.Keys) == 0 {
        return nil, &JWKLookupError{Description: "The retrieved JSON Web Key Set does not contain any key."}
    }

    keys := jwks.Keys
    if kid != "" {
        keys = jwks.Key(kid)
    }

    if len(keys) == 0 {
        return nil, &JWKLookupError{Description: fmt.Sprintf("The JSON Web Token uses signing key with kid '%s' which was not found", kid)}
    }

    var matched []jose.JSONWebKey
    for _, k := range keys {
        if k.Use == use && k.Algorithm == alg {
            matched = append(matched, k)
        }
    }

    switch len(matched) {
    case 0:
        return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
    case 1:
        return &matched[0], nil
    default:
        if strict {
            return nil, &JWKLookupError{Description: fmt.Sprintf("Multiple matching keys found for kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
        }
        return &matched[0], nil
    }
}

This refactored version reduces nesting and improves the overall structure of the function.


244-361: LGTM: NewClientSecretJWKFromClient and NewClientSecretJWK functions are well-implemented.

The functions for creating JWKs from client secrets are thorough and follow the specified rules for key derivation. The error handling is consistent, and the code is well-documented.

Consider refactoring the NewClientSecretJWK function to reduce its complexity. You could extract the logic for creating signature and encryption JWKs into separate functions. For example:

func NewClientSecretJWK(ctx context.Context, secret []byte, kid, alg, enc, use string) (*jose.JSONWebKey, error) {
    if len(secret) == 0 {
        return nil, &JWKLookupError{Description: "The client is not configured with a client secret that can be used for symmetric algorithms"}
    }

    switch use {
    case JSONWebTokenUseSignature:
        return newSignatureJWK(secret, kid, alg)
    case JSONWebTokenUseEncryption:
        return newEncryptionJWK(secret, kid, alg, enc)
    default:
        return &jose.JSONWebKey{
            Key:       secret,
            KeyID:     kid,
            Algorithm: alg,
            Use:       use,
        }, nil
    }
}

func newSignatureJWK(secret []byte, kid, alg string) (*jose.JSONWebKey, error) {
    // Implementation for signature JWK
}

func newEncryptionJWK(secret []byte, kid, alg, enc string) (*jose.JSONWebKey, error) {
    // Implementation for encryption JWK
}

This refactoring would improve readability and make the function easier to maintain.


363-469: LGTM: Utility functions are well-implemented with room for minor improvements.

The utility functions provide a good set of tools for JWT operations. The UnsafeParseSignedAny function is correctly marked as unsafe for production use.

  1. In the EncodeCompactSigned and EncodeNestedCompactEncrypted functions, consider adding error checking for the SetJWS and SetJWE method calls.

  2. The getJWTSignature function could be simplified using a switch statement:

func getJWTSignature(tokenString string) (signature string, err error) {
    switch segments := strings.SplitN(tokenString, ".", 5); len(segments) {
    case 5:
        return "", errors.New("invalid token: the token is probably encrypted")
    case 3:
        return segments[2], nil
    default:
        return "", fmt.Errorf("invalid token: the format is unknown")
    }
}
  1. The newError function could be simplified using errors.Join from the standard library (Go 1.20+):
func newError(message string, err error, more ...error) error {
    errors := make([]error, 0, len(more)+2)
    errors = append(errors, err)
    if message != "" {
        errors = append(errors, fmt.Errorf(message))
    }
    errors = append(errors, more...)
    return errors.Join(errors...)
}

These changes would improve the readability and maintainability of the code.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3d978f4 and 2a7c7fa.

📒 Files selected for processing (3)
  • token/jwt/claims_id_token.go (4 hunks)
  • token/jwt/claims_id_token_test.go (3 hunks)
  • token/jwt/util.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • token/jwt/claims_id_token_test.go
🔇 Additional comments (7)
token/jwt/claims_id_token.go (4)

20-36: LGTM: Improved claim type definitions

The changes to the IDTokenClaims struct, particularly the use of *NumericDate for time-based claims, enhance compatibility with JWT standards and improve JSON handling. The addition of new fields like ExpirationTime provides more comprehensive claim support.


142-172: LGTM: Safe retrieval methods for time-based claims

The addition of safe retrieval methods for time-based claims (GetExpirationTimeSafe, GetIssuedAtSafe, GetAuthTimeSafe, GetRequestedAtSafe) is a good practice. These methods prevent nil pointer dereferences by returning a zero time when the field is nil, enhancing the overall robustness of the code.


Line range hint 254-341: LGTM: Improved ToMap method

The updates to the ToMap method correctly reflect the changes made to the IDTokenClaims struct. The use of constants from the consts package for claim keys is a good practice that improves consistency and reduces the likelihood of errors due to typos. The method now handles the new field types appropriately, ensuring correct mapping of claims.


363-405: LGTM: Useful helper functions added

The addition of helper functions toNumericDate and toStringSlice is a good improvement. These functions provide robust type conversion for numeric dates and string slices, enhancing the JSON unmarshaling process. The toStringSlice function, in particular, handles various input types gracefully, improving the flexibility of the claims parsing.

token/jwt/util.go (3)

1-22: LGTM: Package declaration, imports, and regular expressions are well-defined.

The package name, imports, and regular expressions for JWT validation are appropriate and correctly implemented.


24-71: LGTM: Utility functions for JWT and algorithm checks are well-implemented.

The functions for checking JWT types and algorithms are correctly implemented and cover a wide range of cases. The code is clear and easy to understand.


1-469: Overall, the JWT utility implementation is robust and well-structured.

This file provides a comprehensive set of functions for JWT and JWK handling. The code is generally well-organized, documented, and implements proper error handling. The suggested improvements throughout the review are minor and mostly relate to code organization, readability, and small optimizations.

Key strengths:

  1. Thorough implementation of JWT and JWK operations
  2. Consistent error handling using custom error types
  3. Well-documented functions with clear purposes

Areas for potential improvement:

  1. Further modularization of some larger functions
  2. Minor readability enhancements in certain areas
  3. Possible performance optimizations in some utility functions

These improvements can be addressed in future iterations to further enhance the quality of the codebase.

Copy link

@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: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 2a7c7fa and 94f63ef.

📒 Files selected for processing (1)
  • token/jwt/claims_id_token.go (4 hunks)
🔇 Additional comments (1)
token/jwt/claims_id_token.go (1)

358-368: 🛠️ Refactor suggestion

Use pointer receiver for toNumericDate method

The toNumericDate method uses a value receiver but accesses pointer fields. To prevent unnecessary copying and to access the pointer fields correctly, change the method to use a pointer receiver.

Apply this diff:

-func (c IDTokenClaims) toNumericDate(key string) (date *NumericDate, err error) {
+func (c *IDTokenClaims) toNumericDate(key string) (date *NumericDate, err error) {

Likely invalid or redundant comment.

Comment on lines +46 to +48
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
return toNumericDate(ClaimNotBefore)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix missing receiver in GetNotBefore method

The method GetNotBefore calls toNumericDate without the receiver c., which will result in a compilation error since toNumericDate is a method of IDTokenClaims. Please add the receiver c. to the method call.

Apply this diff to fix the issue:

func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
-    return toNumericDate(ClaimNotBefore)
+    return c.toNumericDate(ClaimNotBefore)
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
return toNumericDate(ClaimNotBefore)
func (c *IDTokenClaims) GetNotBefore() (nbf *NumericDate, err error) {
return c.toNumericDate(ClaimNotBefore)

Comment on lines 62 to 140
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
vopts := &ClaimValidationOptions{}

for _, opt := range opts {
opt(vopts)
}

if c.Issuer != "" {
ret[consts.ClaimIssuer] = c.Issuer
var now int64

if vopts.timef != nil {
now = vopts.timef().UTC().Unix()
} else {
delete(ret, consts.ClaimIssuer)
now = TimeFunc().UTC().Unix()
}

vErr := new(ValidationError)

var date *NumericDate

if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}

if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}

if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}

var str string

if len(vopts.iss) != 0 {
if str, err = c.GetIssuer(); err != nil {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.iss, true) {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
}
}

if len(vopts.sub) != 0 {
if str, err = c.GetSubject(); err != nil {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.sub, true) {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorSubject
}
}

var aud ClaimStrings

if len(vopts.aud) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}

if len(vopts.audAll) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}

if vErr.valid() {
return nil
}

return vErr
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use pointer receiver for Valid method

The Valid method uses a value receiver, but since it accesses pointer fields and for consistency with other methods, consider using a pointer receiver to avoid unnecessary copying and potential issues.

Apply this diff:

-func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
+func (c *IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (c IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
vopts := &ClaimValidationOptions{}
for _, opt := range opts {
opt(vopts)
}
if c.Issuer != "" {
ret[consts.ClaimIssuer] = c.Issuer
var now int64
if vopts.timef != nil {
now = vopts.timef().UTC().Unix()
} else {
delete(ret, consts.ClaimIssuer)
now = TimeFunc().UTC().Unix()
}
vErr := new(ValidationError)
var date *NumericDate
if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}
if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
var str string
if len(vopts.iss) != 0 {
if str, err = c.GetIssuer(); err != nil {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.iss, true) {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
}
}
if len(vopts.sub) != 0 {
if str, err = c.GetSubject(); err != nil {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.sub, true) {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorSubject
}
}
var aud ClaimStrings
if len(vopts.aud) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}
if len(vopts.audAll) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}
if vErr.valid() {
return nil
}
return vErr
}
func (c *IDTokenClaims) Valid(opts ...ClaimValidationOption) (err error) {
vopts := &ClaimValidationOptions{}
for _, opt := range opts {
opt(vopts)
}
var now int64
if vopts.timef != nil {
now = vopts.timef().UTC().Unix()
} else {
now = TimeFunc().UTC().Unix()
}
vErr := new(ValidationError)
var date *NumericDate
if date, err = c.GetExpirationTime(); !validDate(validInt64Future, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}
if date, err = c.GetIssuedAt(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}
if date, err = c.GetNotBefore(); !validDate(validInt64Past, now, vopts.expRequired, date, err) {
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}
var str string
if len(vopts.iss) != 0 {
if str, err = c.GetIssuer(); err != nil {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.iss, true) {
vErr.Inner = errors.New("Token has invalid issuer")
vErr.Errors |= ValidationErrorIssuer
}
}
if len(vopts.sub) != 0 {
if str, err = c.GetSubject(); err != nil {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorIssuer
} else if !validString(str, vopts.sub, true) {
vErr.Inner = errors.New("Token has invalid subject")
vErr.Errors |= ValidationErrorSubject
}
}
var aud ClaimStrings
if len(vopts.aud) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAny(vopts.aud, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}
if len(vopts.audAll) != 0 {
if aud, err = c.GetAudience(); err != nil || aud == nil || !aud.ValidAll(vopts.audAll, true) {
vErr.Inner = errors.New("Token has invalid audience")
vErr.Errors |= ValidationErrorAudience
}
}
if vErr.valid() {
return nil
}
return vErr
}

Copy link

@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: 7

🧹 Outside diff range and nitpick comments (1)
token/jwt/util.go (1)

438-450: Enhance warning for UnsafeParseSignedAny and add documentation.

The UnsafeParseSignedAny function is unsafe for production use.

Add a prominent warning and documentation:

// UnsafeParseSignedAny attempts to parse any signed token without verification.
// WARNING: This function is unsafe for production use and should only be used for testing purposes.
func UnsafeParseSignedAny(tokenString string, dest any) (token *jwt.JSONWebToken, err error) {
    // Function implementation...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 94f63ef and f157069.

📒 Files selected for processing (3)
  • token/jwt/claims_id_token.go (3 hunks)
  • token/jwt/claims_map.go (1 hunks)
  • token/jwt/util.go (1 hunks)
🧰 Additional context used
🔇 Additional comments (7)
token/jwt/claims_id_token.go (4)

39-41: LGTM: GetExpirationTime method correctly implemented

The GetExpirationTime method properly retrieves the ExpirationTime claim and is correctly implemented.


47-49: Fix missing receiver in GetNotBefore method

The GetNotBefore method calls toNumericDate without the receiver c., which will result in a compilation error since toNumericDate is a method of IDTokenClaims. This issue was previously flagged in a past review comment.


63-140: Consider using a pointer receiver for the Valid method

The Valid method uses a value receiver, which may lead to unnecessary copying of the struct and inconsistent receiver types across methods. This concern was previously raised in a past review comment.


174-246: Avoid including claim values in error messages

In the UnmarshalJSON method, the error message includes the claim value, which could potentially expose sensitive information. This issue was previously raised in a past review comment.

token/jwt/util.go (3)

74-144: Consider improving error handling and consistency in header validation functions.

The headerValidateJWS and headerValidateJWE functions perform essential checks but can be enhanced:

  1. Consistency in Error Messages: Ensure error messages are uniform and provide clear information.
  2. Simplify Nested Conditions: Refactor nested if statements for better readability.
  3. Use Helper Functions: Extract repeated logic into helper functions to reduce duplication.

200-243: Address potential non-deterministic key selection in SearchJWKS.

When multiple keys match the criteria and strict is false, the function returns the first matched key. Since the order of keys in jwks.Keys is not guaranteed, this could lead to unpredictable behavior.


308-331: ⚠️ Potential issue

Clarify key length units to prevent confusion between bits and bytes.

In the NewClientSecretJWK function, the variable bits represents the key length in bytes, but the name suggests bits. This can cause confusion.

Rename bits to keyLengthBytes for clarity:

 var (
-    hasher hash.Hash
-    bits   int
+    hasher        hash.Hash
+    keyLengthBytes int
 )

Update the assignments accordingly:

 switch keyAlg {
 case jose.A128KW, jose.A128GCMKW, jose.PBES2_HS256_A128KW:
-    bits = aes.BlockSize
+    keyLengthBytes = aes.BlockSize

Make similar changes for other cases where bits is assigned.

Comment on lines +16 to +19
// NewMapClaims returns a set of MapClaims from an object that has the appropriate JSON tags.
func NewMapClaims(obj any) (claims MapClaims) {
return toMap(obj)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Undefined Function: toMap is not defined

The function toMap used in NewMapClaims is not defined within this file or imported from any package. This will result in a compile-time error.

Please provide the definition of toMap or ensure it is properly imported.

return m.toString(ClaimSubject)
}

// VerifySubject compares the iss claim against cmp.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in Comment: Incorrect Claim Reference

The comment for VerifySubject incorrectly references the 'iss' claim instead of the 'sub' claim. This might cause confusion for future maintainers.

Apply this diff to fix the comment:

-// VerifySubject compares the iss claim against cmp.
+// VerifySubject compares the sub claim against cmp.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// VerifySubject compares the iss claim against cmp.
// VerifySubject compares the sub claim against cmp.

Comment on lines +25 to +37
ExpirationTime *NumericDate `json:"exp"`
IssuedAt *NumericDate `json:"iat"`
AuthTime *NumericDate `json:"auth_time,omitempty"`
RequestedAt *NumericDate `json:"rat,omitempty"`
Nonce string `json:"nonce,omitempty"`
AuthenticationContextClassReference string `json:"acr,omitempty"`
AuthenticationMethodsReferences []string `json:"amr,omitempty"`
AuthorizedParty string `json:"azp,omitempty"`
AccessTokenHash string `json:"at_hash,omitempty"`
CodeHash string `json:"c_hash,omitempty"`
StateHash string `json:"s_hash,omitempty"`
Extra map[string]any `json:"ext,omitempty"`
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing 'Not Before' (nbf) claim in IDTokenClaims struct

The IDTokenClaims struct lacks the NotBefore (nbf) claim, which is a standard JWT claim representing the time before which the token is valid. Since methods like GetNotBefore() expect this field, its absence may lead to runtime errors or incorrect validations.

Consider adding the NotBefore field to the struct:

+	NotBefore *NumericDate `json:"nbf,omitempty"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ExpirationTime *NumericDate `json:"exp"`
IssuedAt *NumericDate `json:"iat"`
AuthTime *NumericDate `json:"auth_time,omitempty"`
RequestedAt *NumericDate `json:"rat,omitempty"`
Nonce string `json:"nonce,omitempty"`
AuthenticationContextClassReference string `json:"acr,omitempty"`
AuthenticationMethodsReferences []string `json:"amr,omitempty"`
AuthorizedParty string `json:"azp,omitempty"`
AccessTokenHash string `json:"at_hash,omitempty"`
CodeHash string `json:"c_hash,omitempty"`
StateHash string `json:"s_hash,omitempty"`
Extra map[string]any `json:"ext,omitempty"`
}
ExpirationTime *NumericDate `json:"exp"`
IssuedAt *NumericDate `json:"iat"`
AuthTime *NumericDate `json:"auth_time,omitempty"`
RequestedAt *NumericDate `json:"rat,omitempty"`
NotBefore *NumericDate `json:"nbf,omitempty"`
Nonce string `json:"nonce,omitempty"`
AuthenticationContextClassReference string `json:"acr,omitempty"`
AuthenticationMethodsReferences []string `json:"amr,omitempty"`
AuthorizedParty string `json:"azp,omitempty"`
AccessTokenHash string `json:"at_hash,omitempty"`
CodeHash string `json:"c_hash,omitempty"`
StateHash string `json:"s_hash,omitempty"`
Extra map[string]any `json:"ext,omitempty"`
}

Comment on lines +95 to +144
func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) {
if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) {
return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty")
}

if header.Algorithm == "" {
return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty")
}

var (
value any
ok bool
)

if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) {
if value, ok = header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]; ok {
switch p2c := value.(type) {
case float64:
if p2c > 5000000 {
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': more than 5,000,000", int(p2c))
} else if p2c < 200000 {
return "", "", "", "", fmt.Errorf("jwe header 'p2c' has an invalid value '%d': less than 200,000", int(p2c))
}
default:
return "", "", "", "", fmt.Errorf("jwe header 'p2c' value has invalid type %T", p2c)
}
}
}

if value, ok = header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]; ok {
switch encv := value.(type) {
case string:
if encv != "" {
enc = encv

break
}

return "", "", "", "", fmt.Errorf("jwe header 'enc' value is empty")
default:
return "", "", "", "", fmt.Errorf("jwe header 'enc' value has invalid type %T", encv)
}
}

if value, ok = header.ExtraHeaders[JSONWebTokenHeaderContentType]; ok {
cty, _ = value.(string)
}

return header.KeyID, header.Algorithm, enc, cty, nil
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor headerValidateJWE for improved readability and error handling.

To enhance the headerValidateJWE function, consider the following refactoring:

  • Extract Validation Logic into Helper Functions: Separate the validation of p2c and enc into dedicated functions.
  • Improve Error Messages: Provide detailed and consistent error messages for easier debugging.

Apply this refactored version:

func headerValidateJWE(header jose.Header) (kid, alg, enc, cty string, err error) {
    if header.Algorithm == "" {
        return "", "", "", "", fmt.Errorf("jwe header 'alg' value is missing or empty")
    }

    if header.KeyID == "" && !IsEncryptedJWTClientSecretAlg(header.Algorithm) {
        return "", "", "", "", fmt.Errorf("jwe header 'kid' value is missing or empty")
    }

    if IsEncryptedJWTPasswordBasedAlg(jose.KeyAlgorithm(header.Algorithm)) {
        if err := validatePBES2Count(header); err != nil {
            return "", "", "", "", err
        }
    }

    enc, err = validateEncryptionAlgorithm(header)
    if err != nil {
        return "", "", "", "", err
    }

    cty, _ = header.ExtraHeaders[JSONWebTokenHeaderContentType].(string)

    return header.KeyID, header.Algorithm, enc, cty, nil
}

func validatePBES2Count(header jose.Header) error {
    value, ok := header.ExtraHeaders[JSONWebTokenHeaderPBES2Count]
    if !ok {
        return nil
    }

    p2c, ok := value.(float64)
    if !ok {
        return fmt.Errorf("jwe header 'p2c' value has invalid type %T", value)
    }

    if p2c < 200000 || p2c > 5000000 {
        return fmt.Errorf("jwe header 'p2c' has an invalid value '%d': must be between 200,000 and 5,000,000", int(p2c))
    }

    return nil
}

func validateEncryptionAlgorithm(header jose.Header) (string, error) {
    value, ok := header.ExtraHeaders[JSONWebTokenHeaderEncryptionAlgorithm]
    if !ok {
        return "", nil
    }

    enc, ok := value.(string)
    if !ok {
        return "", fmt.Errorf("jwe header 'enc' value has invalid type %T", value)
    }

    if enc == "" {
        return "", fmt.Errorf("jwe header 'enc' value is empty")
    }

    return enc, nil
}

Comment on lines +472 to +505
func toMap(obj any) (result map[string]any) {
result = map[string]any{}

if obj == nil {
return result
}

v := reflect.TypeOf(obj)

reflectValue := reflect.ValueOf(obj)
reflectValue = reflect.Indirect(reflectValue)

if v.Kind() == reflect.Ptr {
v = v.Elem()
}

for i := 0; i < v.NumField(); i++ {
tag, opts := parseTag(v.Field(i).Tag.Get("json"))
field := reflectValue.Field(i).Interface()
if tag != "" && tag != "-" {
if opts.Contains("omitempty") && isEmptyValue(reflect.ValueOf(field)) {
continue
}

if v.Field(i).Type.Kind() == reflect.Struct {
result[tag] = toMap(field)
} else {
result[tag] = field
}
}
}

return result
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize toMap function for better performance and simplicity.

The toMap function can be simplified by using JSON marshaling.

Consider using json.Marshal and json.Unmarshal:

import "encoding/json"

func toMap(obj any) (result map[string]any) {
    var inrec, outrec []byte
    var err error

    if inrec, err = json.Marshal(obj); err != nil {
        return nil
    }
    if err = json.Unmarshal(inrec, &result); err != nil {
        return nil
    }
    return result
}

This approach leverages standard library functions for serialization, reducing complexity.

Comment on lines +364 to +394
// EncodeCompactSigned helps encoding a token using a signature backed compact encoding.
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) {
token := New()

if headers == nil {
headers = &Headers{}
}

token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm))

return token.CompactSigned(key)
}

// EncodeNestedCompactEncrypted helps encoding a token using a signature backed compact encoding, then nests that within
// an encrypted compact encoded JWT.
func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) {
token := New()

if headers == nil {
headers = &Headers{}
}

if headersJWE == nil {
headersJWE = &Headers{}
}

token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm))
token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE)

return token.CompactEncrypted(keySig, keyEnc)
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling in JWT encoding functions.

The EncodeCompactSigned and EncodeNestedCompactEncrypted functions lack error checks after critical operations.

For EncodeCompactSigned, add error handling after setting the JWS:

 token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm))
+if err != nil {
+    return "", "", fmt.Errorf("failed to set JWS: %w", err)
+}

Similarly, add error handling in EncodeNestedCompactEncrypted:

 token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm))
+if err != nil {
+    return "", "", fmt.Errorf("failed to set JWS: %w", err)
+}
 token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE)
+if err != nil {
+    return "", "", fmt.Errorf("failed to set JWE: %w", err)
+}

This ensures that any failures during token setup are caught and properly handled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// EncodeCompactSigned helps encoding a token using a signature backed compact encoding.
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) {
token := New()
if headers == nil {
headers = &Headers{}
}
token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm))
return token.CompactSigned(key)
}
// EncodeNestedCompactEncrypted helps encoding a token using a signature backed compact encoding, then nests that within
// an encrypted compact encoded JWT.
func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) {
token := New()
if headers == nil {
headers = &Headers{}
}
if headersJWE == nil {
headersJWE = &Headers{}
}
token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm))
token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE)
return token.CompactEncrypted(keySig, keyEnc)
}
// EncodeCompactSigned helps encoding a token using a signature backed compact encoding.
func EncodeCompactSigned(ctx context.Context, claims Claims, headers Mapper, key *jose.JSONWebKey) (tokenString string, signature string, err error) {
token := New()
if headers == nil {
headers = &Headers{}
}
err = token.SetJWS(headers, claims, key.KeyID, jose.SignatureAlgorithm(key.Algorithm))
if err != nil {
return "", "", fmt.Errorf("failed to set JWS: %w", err)
}
return token.CompactSigned(key)
}
// EncodeNestedCompactEncrypted helps encoding a token using a signature backed compact encoding, then nests that within
// an encrypted compact encoded JWT.
func EncodeNestedCompactEncrypted(ctx context.Context, claims Claims, headers, headersJWE Mapper, keySig, keyEnc *jose.JSONWebKey, enc jose.ContentEncryption) (tokenString string, signature string, err error) {
token := New()
if headers == nil {
headers = &Headers{}
}
if headersJWE == nil {
headersJWE = &Headers{}
}
err = token.SetJWS(headers, claims, keySig.KeyID, jose.SignatureAlgorithm(keySig.Algorithm))
if err != nil {
return "", "", fmt.Errorf("failed to set JWS: %w", err)
}
err = token.SetJWE(headersJWE, keyEnc.KeyID, jose.KeyAlgorithm(keyEnc.Algorithm), enc, jose.NONE)
if err != nil {
return "", "", fmt.Errorf("failed to set JWE: %w", err)
}
return token.CompactEncrypted(keySig, keyEnc)
}

Comment on lines +231 to +242
switch len(matched) {
case 1:
return &matched[0], nil
case 0:
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
default:
if strict {
return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
}

return &matched[0], nil
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure deterministic key selection when multiple keys match.

To prevent non-deterministic behavior in SearchJWKS, consider implementing a deterministic key selection mechanism:

  • Sort the Keys: Sort the matched keys based on a consistent attribute (e.g., KeyID).
  • Require Specific Parameters: Enforce stricter criteria or require additional parameters to uniquely identify the key.

Apply this diff to enhance the function:

+import "sort"

 func SearchJWKS(jwks *jose.JSONWebKeySet, kid, alg, use string, strict bool) (key *jose.JSONWebKey, err error) {
     // ... existing code ...

     switch len(matched) {
     case 1:
         return &matched[0], nil
     case 0:
         return nil, &JWKLookupError{Description: fmt.Sprintf("Unable to find JSON web key with kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
     default:
+        // Sort the matched keys to ensure deterministic selection
+        sort.SliceStable(matched, func(i, j int) bool {
+            return matched[i].KeyID < matched[j].KeyID
+        })
+
         if strict {
             return nil, &JWKLookupError{Description: fmt.Sprintf("Multiple matching keys found for kid '%s', use '%s', and alg '%s' in JSON Web Key Set", kid, use, alg)}
         }

         return &matched[0], nil
     }
 }

Committable suggestion was skipped due to low confidence.

@james-d-elliott james-d-elliott force-pushed the feat-jwe branch 9 times, most recently from 53abe26 to b11ab45 Compare December 8, 2024 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant