nui-lambda-shared-utils 1.1.5__tar.gz → 1.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/CLAUDE.md +2 -0
  2. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/PKG-INFO +28 -1
  3. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/README.md +20 -0
  4. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/README.md +3 -1
  5. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/getting-started/configuration.md +11 -0
  6. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/getting-started/installation.md +7 -0
  7. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/getting-started/quickstart.md +36 -1
  8. nui_lambda_shared_utils-1.2.1/docs/guides/jwt-authentication.md +148 -0
  9. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/__init__.py +25 -0
  10. nui_lambda_shared_utils-1.2.1/nui_lambda_shared_utils/jwt_auth.py +277 -0
  11. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils.egg-info/SOURCES.txt +3 -0
  12. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/pyproject.toml +6 -0
  13. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/requirements-test.txt +3 -1
  14. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/setup.py +10 -0
  15. nui_lambda_shared_utils-1.2.1/tests/test_jwt_auth.py +388 -0
  16. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/.editorconfig +0 -0
  17. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/.github/workflows/ci.yml +0 -0
  18. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/.github/workflows/publish.yml +0 -0
  19. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/.github/workflows/test.yml +0 -0
  20. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/.markdownlint-cli2.yaml +0 -0
  21. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/CONTRIBUTING.md +0 -0
  22. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/LICENSE +0 -0
  23. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/MANIFEST.in +0 -0
  24. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/development/testing.md +0 -0
  25. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/cli-tools.md +0 -0
  26. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/elasticsearch-integration.md +0 -0
  27. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/lambda-utilities.md +0 -0
  28. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/log-processing.md +0 -0
  29. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/powertools-integration.md +0 -0
  30. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/shared-types.md +0 -0
  31. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/docs/guides/slack-integration.md +0 -0
  32. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/mypy.ini +0 -0
  33. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/base_client.py +0 -0
  34. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/cli.py +0 -0
  35. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/cloudwatch_metrics.py +0 -0
  36. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/config.py +0 -0
  37. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/db_client.py +0 -0
  38. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/error_handler.py +0 -0
  39. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/es_client.py +0 -0
  40. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/es_query_builder.py +0 -0
  41. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/lambda_helpers.py +0 -0
  42. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/log_processors.py +0 -0
  43. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/powertools_helpers.py +0 -0
  44. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/secrets_helper.py +0 -0
  45. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/slack_client.py +0 -0
  46. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/slack_formatter.py +0 -0
  47. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/slack_setup/__init__.py +0 -0
  48. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/slack_setup/channel_creator.py +0 -0
  49. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/slack_setup/channel_definitions.py +0 -0
  50. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/slack_setup/setup_helpers.py +0 -0
  51. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/timezone.py +0 -0
  52. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/nui_lambda_shared_utils/utils.py +0 -0
  53. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/pytest.ini +0 -0
  54. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/setup.cfg +0 -0
  55. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/__init__.py +0 -0
  56. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_aws_utils.py +0 -0
  57. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_base_client.py +0 -0
  58. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_cloudwatch_metrics.py +0 -0
  59. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_config.py +0 -0
  60. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_db_client.py +0 -0
  61. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_error_handler.py +0 -0
  62. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_es_client.py +0 -0
  63. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_es_query_builder.py +0 -0
  64. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_lambda_helpers.py +0 -0
  65. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_log_processors.py +0 -0
  66. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_powertools_helpers.py +0 -0
  67. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_secrets_helper.py +0 -0
  68. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_slack_client.py +0 -0
  69. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_slack_formatter.py +0 -0
  70. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_timezone.py +0 -0
  71. {nui_lambda_shared_utils-1.1.5 → nui_lambda_shared_utils-1.2.1}/tests/test_utils.py +0 -0
@@ -37,6 +37,7 @@ This is `nui-lambda-shared-utils`, a Python package providing production-ready u
37
37
  - **Elasticsearch** (`es_client.py`, `es_query_builder.py`) - Query builders and health monitoring
38
38
  - **Database** (`db_client.py`) - Connection pooling with retry logic
39
39
  - **Metrics** (`cloudwatch_metrics.py`) - Batched CloudWatch publishing with decorators
40
+ - **JWT Authentication** (`jwt_auth.py`) - RS256 token validation for API Gateway Lambdas (uses `rsa` package)
40
41
  - **Error Handling** (`error_handler.py`) - Retry patterns with exponential backoff
41
42
  - **Timezone Utils** (`timezone.py`) - Timezone conversion and formatting utilities
42
43
 
@@ -47,6 +48,7 @@ The package uses optional extras to minimize Lambda bundle size:
47
48
  - `elasticsearch` - Elasticsearch client and query builders
48
49
  - `database` - MySQL/PostgreSQL drivers
49
50
  - `slack` - Slack SDK
51
+ - `jwt` - RS256 JWT validation (`rsa` package)
50
52
  - `all` - All integrations
51
53
  - `dev` - Development and testing tools
52
54
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nui-lambda-shared-utils
3
- Version: 1.1.5
3
+ Version: 1.2.1
4
4
  Summary: Enterprise-grade utilities for AWS Lambda functions with Slack, Elasticsearch, and monitoring integrations
5
5
  Home-page: https://github.com/nuimarkets/nui-lambda-shared-utils
6
6
  Author: NUI Markets
@@ -41,6 +41,8 @@ Requires-Dist: slack-sdk>=3.19.0; extra == "slack"
41
41
  Provides-Extra: powertools
42
42
  Requires-Dist: aws-lambda-powertools<4.0.0,>=3.6.0; extra == "powertools"
43
43
  Requires-Dist: coloredlogs>=15.0; extra == "powertools"
44
+ Provides-Extra: jwt
45
+ Requires-Dist: rsa>=4.9; extra == "jwt"
44
46
  Provides-Extra: all
45
47
  Requires-Dist: elasticsearch<8.0.0,>=7.17.0; extra == "all"
46
48
  Requires-Dist: pymysql>=1.0.0; extra == "all"
@@ -48,6 +50,7 @@ Requires-Dist: psycopg2-binary>=2.9.0; extra == "all"
48
50
  Requires-Dist: slack-sdk>=3.19.0; extra == "all"
49
51
  Requires-Dist: aws-lambda-powertools<4.0.0,>=3.6.0; extra == "all"
50
52
  Requires-Dist: coloredlogs>=15.0; extra == "all"
53
+ Requires-Dist: rsa>=4.9; extra == "all"
51
54
  Provides-Extra: dev
52
55
  Requires-Dist: pytest>=7.0.0; extra == "dev"
53
56
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -56,8 +59,12 @@ Requires-Dist: moto>=4.0.0; extra == "dev"
56
59
  Requires-Dist: black>=22.0.0; extra == "dev"
57
60
  Requires-Dist: mypy>=0.990; extra == "dev"
58
61
  Requires-Dist: boto3-stubs[essential]>=1.20.0; extra == "dev"
62
+ Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
63
+ Requires-Dist: types-pytz>=2021.3.0; extra == "dev"
59
64
  Requires-Dist: twine>=4.0.0; extra == "dev"
60
65
  Requires-Dist: build>=0.8.0; extra == "dev"
66
+ Requires-Dist: rsa>=4.9; extra == "dev"
67
+ Requires-Dist: cryptography>=41.0.0; extra == "dev"
61
68
  Dynamic: author
62
69
  Dynamic: home-page
63
70
  Dynamic: license-file
@@ -99,6 +106,7 @@ Production-ready utilities for AWS Lambda functions with Slack, Elasticsearch, d
99
106
  - **Elasticsearch Operations** - Query builders, index management, and health monitoring
100
107
  - **Database Connections** - Connection pooling, automatic retries, and transaction management
101
108
  - **CloudWatch Metrics** - Batched publishing with custom dimensions
109
+ - **JWT Authentication** - RS256 token validation for API Gateway Lambdas (lightweight, no PyJWT needed)
102
110
  - **Error Handling** - Intelligent retry patterns with exponential backoff
103
111
  - **Timezone Utilities** - Timezone handling and formatting
104
112
  - **Configurable Defaults** - Environment-aware configuration system
@@ -134,6 +142,7 @@ pip install nui-lambda-shared-utils[powertools] # AWS Powertools only
134
142
  pip install nui-lambda-shared-utils[slack] # Slack only
135
143
  pip install nui-lambda-shared-utils[elasticsearch] # Elasticsearch only
136
144
  pip install nui-lambda-shared-utils[database] # Database only
145
+ pip install nui-lambda-shared-utils[jwt] # JWT authentication only
137
146
  ```
138
147
 
139
148
  ### Basic Configuration
@@ -276,6 +285,23 @@ def lambda_handler(event, context):
276
285
 
277
286
  **[→ See full metrics guide](docs/getting-started/quickstart.md#cloudwatch-metrics)**
278
287
 
288
+ ### JWT Authentication
289
+
290
+ ```python
291
+ from nui_lambda_shared_utils import require_auth, AuthenticationError
292
+
293
+ def lambda_handler(event, context):
294
+ try:
295
+ claims = require_auth(event) # Validates Bearer token from Authorization header
296
+ except AuthenticationError as e:
297
+ return {"statusCode": 401, "body": "Unauthorized"}
298
+
299
+ user_id = claims["sub"]
300
+ return {"statusCode": 200, "body": f"Hello {user_id}"}
301
+ ```
302
+
303
+ **[→ See JWT authentication guide](docs/guides/jwt-authentication.md)**
304
+
279
305
  ### Error Handling
280
306
 
281
307
  ```python
@@ -330,6 +356,7 @@ This package requires AWS Secrets Manager for credential storage and IAM permiss
330
356
  - Elasticsearch: `{"host": "...", "username": "...", "password": "..."}`
331
357
  - Database: `{"host": "...", "port": 3306, "username": "...", "password": "...", "database": "..."}`
332
358
  - Slack: `{"bot_token": "xoxb-...", "webhook_url": "..."}`
359
+ - JWT Public Key: `{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\n..."}`
333
360
 
334
361
  **IAM Permissions** - Lambda execution role needs:
335
362
 
@@ -34,6 +34,7 @@ Production-ready utilities for AWS Lambda functions with Slack, Elasticsearch, d
34
34
  - **Elasticsearch Operations** - Query builders, index management, and health monitoring
35
35
  - **Database Connections** - Connection pooling, automatic retries, and transaction management
36
36
  - **CloudWatch Metrics** - Batched publishing with custom dimensions
37
+ - **JWT Authentication** - RS256 token validation for API Gateway Lambdas (lightweight, no PyJWT needed)
37
38
  - **Error Handling** - Intelligent retry patterns with exponential backoff
38
39
  - **Timezone Utilities** - Timezone handling and formatting
39
40
  - **Configurable Defaults** - Environment-aware configuration system
@@ -69,6 +70,7 @@ pip install nui-lambda-shared-utils[powertools] # AWS Powertools only
69
70
  pip install nui-lambda-shared-utils[slack] # Slack only
70
71
  pip install nui-lambda-shared-utils[elasticsearch] # Elasticsearch only
71
72
  pip install nui-lambda-shared-utils[database] # Database only
73
+ pip install nui-lambda-shared-utils[jwt] # JWT authentication only
72
74
  ```
73
75
 
74
76
  ### Basic Configuration
@@ -211,6 +213,23 @@ def lambda_handler(event, context):
211
213
 
212
214
  **[→ See full metrics guide](docs/getting-started/quickstart.md#cloudwatch-metrics)**
213
215
 
216
+ ### JWT Authentication
217
+
218
+ ```python
219
+ from nui_lambda_shared_utils import require_auth, AuthenticationError
220
+
221
+ def lambda_handler(event, context):
222
+ try:
223
+ claims = require_auth(event) # Validates Bearer token from Authorization header
224
+ except AuthenticationError as e:
225
+ return {"statusCode": 401, "body": "Unauthorized"}
226
+
227
+ user_id = claims["sub"]
228
+ return {"statusCode": 200, "body": f"Hello {user_id}"}
229
+ ```
230
+
231
+ **[→ See JWT authentication guide](docs/guides/jwt-authentication.md)**
232
+
214
233
  ### Error Handling
215
234
 
216
235
  ```python
@@ -265,6 +284,7 @@ This package requires AWS Secrets Manager for credential storage and IAM permiss
265
284
  - Elasticsearch: `{"host": "...", "username": "...", "password": "..."}`
266
285
  - Database: `{"host": "...", "port": 3306, "username": "...", "password": "...", "database": "..."}`
267
286
  - Slack: `{"bot_token": "xoxb-...", "webhook_url": "..."}`
287
+ - JWT Public Key: `{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\n..."}`
268
288
 
269
289
  **IAM Permissions** - Lambda execution role needs:
270
290
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Welcome to the comprehensive documentation for `nui-lambda-shared-utils`.
4
4
 
5
- **Last Updated**: 2025-01-19
5
+ **Last Updated**: 2026-02-13
6
6
 
7
7
  ## Quick Navigation
8
8
 
@@ -20,6 +20,7 @@ Component-specific guides for major features:
20
20
  - **[Lambda Context Helpers](guides/lambda-utilities.md)** - Environment info extraction for logging and metrics
21
21
  - **[Slack Integration](guides/slack-integration.md)** - Messaging, formatting, and file uploads
22
22
  - **[Elasticsearch Integration](guides/elasticsearch-integration.md)** - Search, bulk indexing, health checks
23
+ - **[JWT Authentication](guides/jwt-authentication.md)** - RS256 token validation for API Gateway Lambdas
23
24
  - **[Log Processing](guides/log-processing.md)** - Kinesis log extraction and ES index naming
24
25
  - Database Connections (planned)
25
26
  - Error Handling Patterns (planned)
@@ -123,6 +124,7 @@ When contributing to documentation:
123
124
  - Lambda context helpers guide (guides/lambda-utilities.md)
124
125
  - Slack integration guide (guides/slack-integration.md)
125
126
  - Elasticsearch integration guide (guides/elasticsearch-integration.md)
127
+ - JWT authentication guide (guides/jwt-authentication.md)
126
128
  - Shared types reference (guides/shared-types.md)
127
129
  - CLI tools guide (guides/cli-tools.md)
128
130
  - Testing guide (development/testing.md)
@@ -20,6 +20,7 @@ The package uses a hierarchical configuration system with the following priority
20
20
  | `ES_CREDENTIALS_SECRET` | AWS secret name for Elasticsearch credentials | `elasticsearch-credentials` |
21
21
  | `DB_CREDENTIALS_SECRET` | AWS secret name for database credentials | `database-credentials` |
22
22
  | `SLACK_CREDENTIALS_SECRET` | AWS secret name for Slack credentials | `slack-credentials` |
23
+ | `JWT_PUBLIC_KEY_SECRET` | AWS secret name for JWT public key | *(none)* |
23
24
  | `AWS_REGION` | AWS region for services | `us-east-1` |
24
25
 
25
26
  ### Alternative Variable Names
@@ -143,6 +144,16 @@ The package expects AWS secrets in specific JSON formats:
143
144
 
144
145
  - `webhook_url` - Alternative to bot token for simple messaging
145
146
 
147
+ #### JWT Public Key
148
+
149
+ ```json
150
+ {
151
+ "TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh...\n-----END PUBLIC KEY-----"
152
+ }
153
+ ```
154
+
155
+ The key must be in PKCS#8 PEM format (`BEGIN PUBLIC KEY`). The field name is configurable via the `key_field` parameter on `get_jwt_public_key()`.
156
+
146
157
  ### Creating Secrets
147
158
 
148
159
  #### Using AWS CLI
@@ -31,6 +31,9 @@ pip install nui-lambda-shared-utils[elasticsearch]
31
31
  # Database integration only
32
32
  pip install nui-lambda-shared-utils[database]
33
33
 
34
+ # JWT authentication only
35
+ pip install nui-lambda-shared-utils[jwt]
36
+
34
37
  # All integrations
35
38
  pip install nui-lambda-shared-utils[all]
36
39
 
@@ -75,6 +78,10 @@ pip install -e .[dev]
75
78
 
76
79
  - `slack-sdk>=3.19.0` - Official Slack SDK
77
80
 
81
+ #### jwt
82
+
83
+ - `rsa>=4.9` - Pure Python RSA implementation (~100KB, no C extensions)
84
+
78
85
  #### dev
79
86
 
80
87
  - `pytest>=7.0.0` - Testing framework
@@ -223,7 +223,42 @@ def lambda_handler(event, context):
223
223
  raise
224
224
  ```
225
225
 
226
- ### 5. AWS Powertools Integration
226
+ ### 7. JWT Authentication for API Gateway
227
+
228
+ ```python
229
+ import nui_lambda_shared_utils as nui
230
+
231
+ def lambda_handler(event, context):
232
+ """API Gateway Lambda with JWT auth."""
233
+ # Validate Bearer token from Authorization header
234
+ # Fetches public key from Secrets Manager (cached automatically)
235
+ try:
236
+ claims = nui.require_auth(event)
237
+ except nui.AuthenticationError:
238
+ return {"statusCode": 401, "body": "Unauthorized"}
239
+
240
+ # Access token claims
241
+ user_id = claims["sub"]
242
+ role = claims.get("role", "user")
243
+
244
+ return {"statusCode": 200, "body": f"Hello {user_id} ({role})"}
245
+ ```
246
+
247
+ **Installation:**
248
+
249
+ ```bash
250
+ pip install nui-lambda-shared-utils[jwt]
251
+ ```
252
+
253
+ **Requirements:**
254
+
255
+ - `JWT_PUBLIC_KEY_SECRET` env var pointing to Secrets Manager secret
256
+ - Secret contains `{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\n..."}` (PKCS#8 PEM)
257
+ - IAM permission: `secretsmanager:GetSecretValue`
258
+
259
+ **See:** [JWT Authentication Guide](../guides/jwt-authentication.md) for key management and advanced usage.
260
+
261
+ ### 8. AWS Powertools Integration
227
262
 
228
263
  For production Lambda functions, use AWS Powertools for standardized logging, metrics, and error handling:
229
264
 
@@ -0,0 +1,148 @@
1
+ # JWT Authentication Guide
2
+
3
+ RS256 JWT token validation for AWS Lambda functions behind API Gateway.
4
+
5
+ ## Overview
6
+
7
+ The `jwt_auth` module validates RS256-signed JWTs using public keys stored in AWS Secrets Manager. It uses the pure-Python `rsa` package (~100KB) instead of PyJWT or `cryptography` (~35MB), keeping Lambda bundles small without needing a Lambda Layer.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install nui-lambda-shared-utils[jwt]
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ from nui_lambda_shared_utils import require_auth, AuthenticationError
19
+
20
+ def lambda_handler(event, context):
21
+ try:
22
+ claims = require_auth(event)
23
+ except AuthenticationError:
24
+ return {"statusCode": 401, "body": "Unauthorized"}
25
+
26
+ return {"statusCode": 200, "body": f"Hello {claims['sub']}"}
27
+ ```
28
+
29
+ ## API Reference
30
+
31
+ ### `require_auth(event, secret_name=None) -> dict`
32
+
33
+ One-call authentication for API Gateway Lambda handlers. Extracts the Bearer token from the Authorization header, fetches the public key from Secrets Manager, and validates the token.
34
+
35
+ - **event** — API Gateway Lambda proxy event (v1 REST or v2 HTTP API)
36
+ - **secret_name** — Secrets Manager secret name (falls back to `JWT_PUBLIC_KEY_SECRET` env var)
37
+ - **Returns** — Decoded claims dict
38
+ - **Raises** — `AuthenticationError` on any failure (missing header, bad token, expired, bad signature, missing key)
39
+
40
+ ### `validate_jwt(token, public_key) -> dict`
41
+
42
+ Lower-level validation when you already have the public key. Verifies token structure, RS256 signature, and expiration.
43
+
44
+ - **token** — Raw JWT string (no `Bearer` prefix)
45
+ - **public_key** — `rsa.PublicKey` instance
46
+ - **Returns** — Decoded claims dict
47
+ - **Raises** — `JWTValidationError` on failure
48
+
49
+ ### `get_jwt_public_key(secret_name=None, key_field="TOKEN_PUBLIC_KEY") -> rsa.PublicKey`
50
+
51
+ Fetches and parses the PEM public key from Secrets Manager. Uses `secrets_helper` cache — repeated calls don't make additional API calls.
52
+
53
+ - **secret_name** — Falls back to `JWT_PUBLIC_KEY_SECRET` env var
54
+ - **key_field** — JSON field in the secret containing the PEM string
55
+ - **Returns** — `rsa.PublicKey`
56
+ - **Raises** — `JWTValidationError` if secret or field is missing/invalid
57
+
58
+ ### Exceptions
59
+
60
+ - **`JWTValidationError`** — Base exception for token validation failures
61
+ - **`AuthenticationError(JWTValidationError)`** — Authentication-level failures (what `require_auth` raises)
62
+
63
+ ## AWS Setup
64
+
65
+ ### 1. Store the Public Key
66
+
67
+ ```bash
68
+ aws secretsmanager create-secret \
69
+ --name "prod/jwt-public-key" \
70
+ --secret-string '{"TOKEN_PUBLIC_KEY": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh...\n-----END PUBLIC KEY-----"}'
71
+ ```
72
+
73
+ The key must be PKCS#8 PEM format (`BEGIN PUBLIC KEY`, not `BEGIN RSA PUBLIC KEY`).
74
+
75
+ ### 2. Lambda Environment
76
+
77
+ ```yaml
78
+ # serverless.yml
79
+ environment:
80
+ JWT_PUBLIC_KEY_SECRET: prod/jwt-public-key
81
+ ```
82
+
83
+ ### 3. IAM Permissions
84
+
85
+ ```yaml
86
+ # serverless.yml
87
+ iamRoleStatements:
88
+ - Effect: Allow
89
+ Action: secretsmanager:GetSecretValue
90
+ Resource: arn:aws:secretsmanager:*:*:secret:prod/jwt-public-key-*
91
+ ```
92
+
93
+ ## Usage Patterns
94
+
95
+ ### Skip Auth for Health Endpoints
96
+
97
+ ```python
98
+ def lambda_handler(event, context):
99
+ path = event.get("path") or event.get("rawPath", "")
100
+ if path == "/health":
101
+ return {"statusCode": 200, "body": "ok"}
102
+
103
+ try:
104
+ claims = require_auth(event)
105
+ except AuthenticationError:
106
+ return {"statusCode": 401, "body": "Unauthorized"}
107
+
108
+ # Authenticated route handling
109
+ return handle_request(event, claims)
110
+ ```
111
+
112
+ ### Direct Validation (Pre-fetched Key)
113
+
114
+ ```python
115
+ from nui_lambda_shared_utils import get_jwt_public_key, validate_jwt, JWTValidationError
116
+
117
+ # Fetch key once at module level (cached by secrets_helper)
118
+ public_key = get_jwt_public_key(secret_name="prod/jwt-public-key")
119
+
120
+ def validate_token(token: str) -> dict:
121
+ try:
122
+ return validate_jwt(token, public_key)
123
+ except JWTValidationError as e:
124
+ raise ValueError(f"Bad token: {e}")
125
+ ```
126
+
127
+ ### Custom Key Field Name
128
+
129
+ ```python
130
+ # If your secret uses a different field name
131
+ key = get_jwt_public_key(
132
+ secret_name="prod/auth-keys",
133
+ key_field="RSA_PUBLIC_KEY"
134
+ )
135
+ ```
136
+
137
+ ## What Gets Validated
138
+
139
+ | Check | Behavior |
140
+ |-------|----------|
141
+ | Token structure | Must be 3 dot-separated base64url segments |
142
+ | Algorithm | Header `alg` must be `RS256` |
143
+ | Signature | RSA PKCS#1 v1.5 with SHA-256 |
144
+ | Expiration | `exp` claim checked against current time (optional — tokens without `exp` are accepted) |
145
+
146
+ ## Dependency Details
147
+
148
+ The module uses `rsa` (pure Python, ~100KB) for signature verification at runtime. This avoids the ~35MB `cryptography` C extension that PyJWT requires for RS256. The `cryptography` package is only needed in dev for generating test key pairs and signing test tokens.
@@ -132,6 +132,24 @@ from .log_processors import (
132
132
  # Lambda context helpers (no external dependencies)
133
133
  from .lambda_helpers import get_lambda_environment_info
134
134
 
135
+ # JWT authentication - optional import
136
+ try:
137
+ from .jwt_auth import (
138
+ validate_jwt,
139
+ require_auth,
140
+ check_auth,
141
+ get_jwt_public_key,
142
+ JWTValidationError,
143
+ AuthenticationError,
144
+ )
145
+ except ImportError:
146
+ validate_jwt = None # type: ignore
147
+ require_auth = None # type: ignore
148
+ check_auth = None # type: ignore
149
+ get_jwt_public_key = None # type: ignore
150
+ JWTValidationError = None # type: ignore
151
+ AuthenticationError = None # type: ignore
152
+
135
153
  # Slack setup utilities (for CLI usage) - optional import
136
154
  try:
137
155
  from . import slack_setup
@@ -224,4 +242,11 @@ __all__ = [
224
242
  "CloudWatchLogsData",
225
243
  # Lambda context helpers
226
244
  "get_lambda_environment_info",
245
+ # JWT authentication
246
+ "validate_jwt",
247
+ "require_auth",
248
+ "check_auth",
249
+ "get_jwt_public_key",
250
+ "JWTValidationError",
251
+ "AuthenticationError",
227
252
  ]