Skip to content

feat: add JWT claims extraction plugin (Issue #1439)-sweng5#2853

Merged
crivetimihai merged 4 commits intoIBM:mainfrom
yiannis2804:feature/issue-1439-jwt-claims-extraction
Feb 14, 2026
Merged

feat: add JWT claims extraction plugin (Issue #1439)-sweng5#2853
crivetimihai merged 4 commits intoIBM:mainfrom
yiannis2804:feature/issue-1439-jwt-claims-extraction

Conversation

@yiannis2804
Copy link
Copy Markdown
Contributor

🔗 Related Issue

Closes #1439


TCD SwEng Group 5

📝 Summary

Implements JWT claims and metadata extraction plugin for downstream authorization plugins (Cedar, OPA, etc.).

Extracts standard and custom JWT claims after verification and stores them in global_context.metadata["jwt_claims"] for use by policy enforcement plugins.


🏷️ Type of Change

  • Feature / Enhancement
  • Documentation

🧪 Verification

Check Command Status
Lint suite make lint ✅ PASS
Unit tests make test ✅ 5/5 PASS
Coverage ≥ 80% make coverage ✅ 92%

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • Tests added/updated for changes (5 comprehensive tests)
  • Documentation updated (README with Cedar/OPA examples)
  • No secrets or credentials committed

📓 Notes

Features:

  • Extracts standard JWT claims (sub, iss, aud, exp, iat, nbf, jti)
  • Extracts custom claims (roles, permissions, groups, attributes)
  • Supports RFC 9396 Rich Authorization Requests
  • Non-blocking permissive mode
  • Error handling with graceful fallback

Implementation:

  • Hooks into HTTP_AUTH_RESOLVE_USER
  • Uses PluginContext correctly to access global_context
  • Returns continue_processing=True for passthrough behavior

Related:

@yiannis2804 yiannis2804 force-pushed the feature/issue-1439-jwt-claims-extraction branch 2 times, most recently from e93a914 to 10fea73 Compare February 11, 2026 19:35
@crivetimihai crivetimihai added this to the Release 1.0.0-GA milestone Feb 12, 2026
@crivetimihai
Copy link
Copy Markdown
Member

Thanks @yiannis2804. Clean plugin implementation that follows the framework patterns.

Code review observations:

  1. jwt.decode with verify_signature=False: The comment says "already verified by auth system" — this is correct for the http_auth_resolve_user hook position. However, document this assumption prominently, because if the hook ordering changes or a misconfiguration allows this plugin to run before auth verification, it would accept forged tokens.
  2. log_claims: true in config.yaml: Logging extracted claims at INFO level (logger.info(f"Extracted JWT claims for user...")) in production could leak PII/sensitive data. Consider using DEBUG level, or only logging the claim count without claim contents.
  3. Token extraction fallback: _extract_token checks payload.credentials then Authorization header. The hasattr(payload.headers, "root") check is brittle — if the headers model changes, this silently fails. Consider using getattr(payload.headers, "root", {}) or the standard header access pattern.
  4. Copyright year: Files say Copyright 2025 but it's 2026.
  5. Coverage badge deleted: The diff deletes docs/docs/images/coverage.svg — was this intentional, or an accident from the branch?

@yiannis2804 yiannis2804 force-pushed the feature/issue-1439-jwt-claims-extraction branch 3 times, most recently from b32a40e to c6da888 Compare February 13, 2026 10:09
@yiannis2804
Copy link
Copy Markdown
Contributor Author

Code Review Response

@crivetimihai Thanks for the detailed review! All feedback has been addressed:

1. ✅ Security documentation (verify_signature=False)

  • Added prominent SECURITY NOTE in module docstring explaining the token pre-verification assumption
  • Added Security Considerations section to README
  • Documented risks if hook ordering changes

2. ✅ Logging sensitivity (PII leak prevention)

  • Changed from logger.info() to logger.debug() for claim extraction
  • Only log claim count and non-sensitive metadata
  • Prevents sensitive data exposure in production logs

3. ✅ Safer header access pattern

  • Replaced hasattr(payload.headers, "root") with getattr(payload.headers, "root", {})
  • More robust against header model changes
  • Fails gracefully with empty dict default

4. ✅ Copyright year correction

  • Updated from 2025 to 2026 in plugin file

5. ✅ Coverage badge deletion

  • The coverage.svg file is auto-generated and listed in .gitignore
  • Not present in upstream/main (IBM also removed it)
  • Correctly excluded from version control as a build artifact

Ready for re-review!

yiannis2804 and others added 3 commits February 14, 2026 16:10
Implements JWT claims and metadata extraction plugin for downstream
authorization plugins (Cedar, OPA, etc.).

Features:
- Extracts standard JWT claims (sub, iss, aud, exp, iat, nbf, jti)
- Extracts custom claims (roles, permissions, groups, attributes)
- Supports RFC 9396 Rich Authorization Requests
- Non-blocking permissive mode
- Stores claims in global_context.metadata['jwt_claims']

Plugin hooks into HTTP_AUTH_RESOLVE_USER to extract claims after JWT
verification and makes them available to policy enforcement plugins.

Implementation:
- Uses PluginContext correctly to access global_context
- Proper hook method naming (http_auth_resolve_user)
- Returns continue_processing=True for passthrough behavior
- Error handling with graceful fallback

Includes:
- Plugin implementation with error handling
- Configuration file (YAML)
- Comprehensive test suite (5 tests, all passing)
- Documentation with usage examples for Cedar/OPA

Testing:
- All 5 plugin tests passing
- Linting clean (ruff + flake8)
- Coverage: 90%

Related to:
- Issue IBM#1439: JWT claims and metadata extraction plugin
- Issue IBM#1422: [EPIC] Agent and tool authentication and authorization
- RFC 9396: Rich Authorization Requests

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>
Addressed all 5 review comments from @[reviewer-name]:

1. Security documentation (verify_signature=False):
   - Added prominent SECURITY NOTE in module docstring
   - Added Security Considerations section to README
   - Documented token pre-verification assumption and risks

2. Logging sensitivity (PII leak prevention):
   - Changed logger.info() to logger.debug() for claim extraction
   - Only log claim count, not claim contents
   - Prevents sensitive data exposure in production logs

3. Safer header access pattern:
   - Replaced hasattr() with getattr(payload.headers, 'root', {})
   - More robust against header model changes
   - Fails gracefully with empty dict default

4. Copyright year correction:
   - Updated from 2025 to 2026 in all files

5. Coverage badge deletion:
   - File is auto-generated (in .gitignore)
   - Not present in upstream/main
   - Correctly excluded from version control

All tests passing (5/5), linting clean, ready for review.

Related to: IBM#1439

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>
- Move plugin from mcpgateway/plugins/ to plugins/ (correct location)
- Rename plugin.py to jwt_claims_extraction.py, config.yaml to
  plugin-manifest.yaml (match naming conventions)
- Fix critical security doc: http_auth_resolve_user fires BEFORE JWT
  verification, not after — document the actual security model
- Write to global_context.state instead of metadata (metadata is
  read-only from plugin perspective per framework docs)
- Add Pydantic config model (JwtClaimsExtractionConfig) with
  configurable context_key
- Add __init__ constructor calling super().__init__(config)
- Add from __future__ import annotations
- Add Bearer scheme validation in _extract_token
- Use lazy logging format strings instead of f-strings
- Remove unnecessary hasattr check on metadata
- Remove error string leak from result metadata
- Fix copyright year consistency (2026 across all files)
- Fix SPDX-License-Identifier: Apache-2.0 headers
- Revert unnecessary MANIFEST.in additions
- Move tests to tests/unit/plugins/ and add new test cases
  for non-Bearer scheme rejection and custom context_key config

Closes IBM#1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai crivetimihai force-pushed the feature/issue-1439-jwt-claims-extraction branch from c6da888 to 838946d Compare February 14, 2026 16:43
@crivetimihai
Copy link
Copy Markdown
Member

crivetimihai commented Feb 14, 2026

Hi @yiannis2804 — thanks for this contribution! The JWT claims extraction plugin is a great idea and fills an important gap for downstream authorization (Cedar, OPA, etc.).

I've rebased your branch onto main, addressed some review items, and force-pushed the result. Here's what changed in the fix-up commit:

Key fixes

Security documentation — The original docstring stated that tokens are pre-verified before http_auth_resolve_user. In fact, this hook fires before JWT signature verification (see auth.py:701). The security model still works correctly (standard auth rejects invalid tokens before any downstream hook reads the claims), but the documentation now accurately describes the actual flow.

global_context.state instead of metadata — The framework docs specify that metadata is read-only from the plugin's perspective. Changed to write to state, which is the correct mutable dict for cross-plugin communication and persists through to http_auth_check_permission where Cedar/OPA plugins will read it.

Was missing: The plugin wasn't registered in plugins/config.yaml — without that entry it would never load at runtime. Added it as mode: "disabled" (users opt in by changing to "permissive")

Consistency with codebase conventions

  • Moved plugin from mcpgateway/plugins/ to plugins/ — all 40+ plugins live in the top-level plugins/ directory
  • Renamed files: plugin.pyjwt_claims_extraction.py, config.yamlplugin-manifest.yaml (matching naming conventions)
  • Added __init__ constructor with Pydantic config model (JwtClaimsExtractionConfig) — all plugins parse their config this way
  • Added from __future__ import annotations (standard in modern plugins)
  • Added Bearer scheme validation in _extract_token — the credentials dict from auth.py includes a scheme field that should be checked
  • Fixed copyright year consistency (2026 across all files)

Test improvements

  • Moved test to tests/unit/plugins/ (correct location for plugins/ directory plugins)
  • Added 2 new test cases: non-Bearer scheme rejection and custom context_key config
  • Updated assertions to check global_context.state instead of metadata
  • All 7 tests pass, linting clean

Please review the changes and let me know if you have any questions.

Adds disabled-by-default registration entry so the plugin can be
activated by setting mode to "permissive".

Closes IBM#1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai crivetimihai merged commit 2934b3e into IBM:main Feb 14, 2026
53 checks passed
suciu-daniel pushed a commit that referenced this pull request Feb 16, 2026
* feat: add JWT claims extraction plugin (Issue #1439)

Implements JWT claims and metadata extraction plugin for downstream
authorization plugins (Cedar, OPA, etc.).

Features:
- Extracts standard JWT claims (sub, iss, aud, exp, iat, nbf, jti)
- Extracts custom claims (roles, permissions, groups, attributes)
- Supports RFC 9396 Rich Authorization Requests
- Non-blocking permissive mode
- Stores claims in global_context.metadata['jwt_claims']

Plugin hooks into HTTP_AUTH_RESOLVE_USER to extract claims after JWT
verification and makes them available to policy enforcement plugins.

Implementation:
- Uses PluginContext correctly to access global_context
- Proper hook method naming (http_auth_resolve_user)
- Returns continue_processing=True for passthrough behavior
- Error handling with graceful fallback

Includes:
- Plugin implementation with error handling
- Configuration file (YAML)
- Comprehensive test suite (5 tests, all passing)
- Documentation with usage examples for Cedar/OPA

Testing:
- All 5 plugin tests passing
- Linting clean (ruff + flake8)
- Coverage: 90%

Related to:
- Issue #1439: JWT claims and metadata extraction plugin
- Issue #1422: [EPIC] Agent and tool authentication and authorization
- RFC 9396: Rich Authorization Requests

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>

* fix: address PR code review feedback

Addressed all 5 review comments from @[reviewer-name]:

1. Security documentation (verify_signature=False):
   - Added prominent SECURITY NOTE in module docstring
   - Added Security Considerations section to README
   - Documented token pre-verification assumption and risks

2. Logging sensitivity (PII leak prevention):
   - Changed logger.info() to logger.debug() for claim extraction
   - Only log claim count, not claim contents
   - Prevents sensitive data exposure in production logs

3. Safer header access pattern:
   - Replaced hasattr() with getattr(payload.headers, 'root', {})
   - More robust against header model changes
   - Fails gracefully with empty dict default

4. Copyright year correction:
   - Updated from 2025 to 2026 in all files

5. Coverage badge deletion:
   - File is auto-generated (in .gitignore)
   - Not present in upstream/main
   - Correctly excluded from version control

All tests passing (5/5), linting clean, ready for review.

Related to: #1439

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>

* fix: align JWT claims extraction plugin with framework conventions

- Move plugin from mcpgateway/plugins/ to plugins/ (correct location)
- Rename plugin.py to jwt_claims_extraction.py, config.yaml to
  plugin-manifest.yaml (match naming conventions)
- Fix critical security doc: http_auth_resolve_user fires BEFORE JWT
  verification, not after — document the actual security model
- Write to global_context.state instead of metadata (metadata is
  read-only from plugin perspective per framework docs)
- Add Pydantic config model (JwtClaimsExtractionConfig) with
  configurable context_key
- Add __init__ constructor calling super().__init__(config)
- Add from __future__ import annotations
- Add Bearer scheme validation in _extract_token
- Use lazy logging format strings instead of f-strings
- Remove unnecessary hasattr check on metadata
- Remove error string leak from result metadata
- Fix copyright year consistency (2026 across all files)
- Fix SPDX-License-Identifier: Apache-2.0 headers
- Revert unnecessary MANIFEST.in additions
- Move tests to tests/unit/plugins/ and add new test cases
  for non-Bearer scheme rejection and custom context_key config

Closes #1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: register JWT claims extraction plugin in config.yaml

Adds disabled-by-default registration entry so the plugin can be
activated by setting mode to "permissive".

Closes #1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
vishu-bh pushed a commit that referenced this pull request Feb 18, 2026
* feat: add JWT claims extraction plugin (Issue #1439)

Implements JWT claims and metadata extraction plugin for downstream
authorization plugins (Cedar, OPA, etc.).

Features:
- Extracts standard JWT claims (sub, iss, aud, exp, iat, nbf, jti)
- Extracts custom claims (roles, permissions, groups, attributes)
- Supports RFC 9396 Rich Authorization Requests
- Non-blocking permissive mode
- Stores claims in global_context.metadata['jwt_claims']

Plugin hooks into HTTP_AUTH_RESOLVE_USER to extract claims after JWT
verification and makes them available to policy enforcement plugins.

Implementation:
- Uses PluginContext correctly to access global_context
- Proper hook method naming (http_auth_resolve_user)
- Returns continue_processing=True for passthrough behavior
- Error handling with graceful fallback

Includes:
- Plugin implementation with error handling
- Configuration file (YAML)
- Comprehensive test suite (5 tests, all passing)
- Documentation with usage examples for Cedar/OPA

Testing:
- All 5 plugin tests passing
- Linting clean (ruff + flake8)
- Coverage: 90%

Related to:
- Issue #1439: JWT claims and metadata extraction plugin
- Issue #1422: [EPIC] Agent and tool authentication and authorization
- RFC 9396: Rich Authorization Requests

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>

* fix: address PR code review feedback

Addressed all 5 review comments from @[reviewer-name]:

1. Security documentation (verify_signature=False):
   - Added prominent SECURITY NOTE in module docstring
   - Added Security Considerations section to README
   - Documented token pre-verification assumption and risks

2. Logging sensitivity (PII leak prevention):
   - Changed logger.info() to logger.debug() for claim extraction
   - Only log claim count, not claim contents
   - Prevents sensitive data exposure in production logs

3. Safer header access pattern:
   - Replaced hasattr() with getattr(payload.headers, 'root', {})
   - More robust against header model changes
   - Fails gracefully with empty dict default

4. Copyright year correction:
   - Updated from 2025 to 2026 in all files

5. Coverage badge deletion:
   - File is auto-generated (in .gitignore)
   - Not present in upstream/main
   - Correctly excluded from version control

All tests passing (5/5), linting clean, ready for review.

Related to: #1439

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>

* fix: align JWT claims extraction plugin with framework conventions

- Move plugin from mcpgateway/plugins/ to plugins/ (correct location)
- Rename plugin.py to jwt_claims_extraction.py, config.yaml to
  plugin-manifest.yaml (match naming conventions)
- Fix critical security doc: http_auth_resolve_user fires BEFORE JWT
  verification, not after — document the actual security model
- Write to global_context.state instead of metadata (metadata is
  read-only from plugin perspective per framework docs)
- Add Pydantic config model (JwtClaimsExtractionConfig) with
  configurable context_key
- Add __init__ constructor calling super().__init__(config)
- Add from __future__ import annotations
- Add Bearer scheme validation in _extract_token
- Use lazy logging format strings instead of f-strings
- Remove unnecessary hasattr check on metadata
- Remove error string leak from result metadata
- Fix copyright year consistency (2026 across all files)
- Fix SPDX-License-Identifier: Apache-2.0 headers
- Revert unnecessary MANIFEST.in additions
- Move tests to tests/unit/plugins/ and add new test cases
  for non-Bearer scheme rejection and custom context_key config

Closes #1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: register JWT claims extraction plugin in config.yaml

Adds disabled-by-default registration entry so the plugin can be
activated by setting mode to "permissive".

Closes #1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Vishu Bhatnagar <vishu.bhatnagar@ibm.com>
kcostell06 pushed a commit to kcostell06/mcp-context-forge that referenced this pull request Feb 24, 2026
)

* feat: add JWT claims extraction plugin (Issue IBM#1439)

Implements JWT claims and metadata extraction plugin for downstream
authorization plugins (Cedar, OPA, etc.).

Features:
- Extracts standard JWT claims (sub, iss, aud, exp, iat, nbf, jti)
- Extracts custom claims (roles, permissions, groups, attributes)
- Supports RFC 9396 Rich Authorization Requests
- Non-blocking permissive mode
- Stores claims in global_context.metadata['jwt_claims']

Plugin hooks into HTTP_AUTH_RESOLVE_USER to extract claims after JWT
verification and makes them available to policy enforcement plugins.

Implementation:
- Uses PluginContext correctly to access global_context
- Proper hook method naming (http_auth_resolve_user)
- Returns continue_processing=True for passthrough behavior
- Error handling with graceful fallback

Includes:
- Plugin implementation with error handling
- Configuration file (YAML)
- Comprehensive test suite (5 tests, all passing)
- Documentation with usage examples for Cedar/OPA

Testing:
- All 5 plugin tests passing
- Linting clean (ruff + flake8)
- Coverage: 90%

Related to:
- Issue IBM#1439: JWT claims and metadata extraction plugin
- Issue IBM#1422: [EPIC] Agent and tool authentication and authorization
- RFC 9396: Rich Authorization Requests

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>

* fix: address PR code review feedback

Addressed all 5 review comments from @[reviewer-name]:

1. Security documentation (verify_signature=False):
   - Added prominent SECURITY NOTE in module docstring
   - Added Security Considerations section to README
   - Documented token pre-verification assumption and risks

2. Logging sensitivity (PII leak prevention):
   - Changed logger.info() to logger.debug() for claim extraction
   - Only log claim count, not claim contents
   - Prevents sensitive data exposure in production logs

3. Safer header access pattern:
   - Replaced hasattr() with getattr(payload.headers, 'root', {})
   - More robust against header model changes
   - Fails gracefully with empty dict default

4. Copyright year correction:
   - Updated from 2025 to 2026 in all files

5. Coverage badge deletion:
   - File is auto-generated (in .gitignore)
   - Not present in upstream/main
   - Correctly excluded from version control

All tests passing (5/5), linting clean, ready for review.

Related to: IBM#1439

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>

* fix: align JWT claims extraction plugin with framework conventions

- Move plugin from mcpgateway/plugins/ to plugins/ (correct location)
- Rename plugin.py to jwt_claims_extraction.py, config.yaml to
  plugin-manifest.yaml (match naming conventions)
- Fix critical security doc: http_auth_resolve_user fires BEFORE JWT
  verification, not after — document the actual security model
- Write to global_context.state instead of metadata (metadata is
  read-only from plugin perspective per framework docs)
- Add Pydantic config model (JwtClaimsExtractionConfig) with
  configurable context_key
- Add __init__ constructor calling super().__init__(config)
- Add from __future__ import annotations
- Add Bearer scheme validation in _extract_token
- Use lazy logging format strings instead of f-strings
- Remove unnecessary hasattr check on metadata
- Remove error string leak from result metadata
- Fix copyright year consistency (2026 across all files)
- Fix SPDX-License-Identifier: Apache-2.0 headers
- Revert unnecessary MANIFEST.in additions
- Move tests to tests/unit/plugins/ and add new test cases
  for non-Bearer scheme rejection and custom context_key config

Closes IBM#1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix: register JWT claims extraction plugin in config.yaml

Adds disabled-by-default registration entry so the plugin can be
activated by setting mode to "permissive".

Closes IBM#1439

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: yiannis2804 <yiannis2804@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
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.

[FEATURE][PLUGIN]: Create JWT claims and metadata extraction plugin

2 participants