From c496315622b7bfa7a41c5e48137d138a88843e65 Mon Sep 17 00:00:00 2001 From: Ishan Jogi Date: Tue, 12 Aug 2025 16:43:51 +0530 Subject: [PATCH 1/6] introducing jwt-parser function Signed-off-by: Ishan Jogi --- functions/jwt-parser/1.0.0/OWNERS.md | 4 + functions/jwt-parser/1.0.0/README.md | 194 +++++++++++++++++++++++ functions/jwt-parser/1.0.0/function.yaml | 30 ++++ 3 files changed, 228 insertions(+) create mode 100644 functions/jwt-parser/1.0.0/OWNERS.md create mode 100644 functions/jwt-parser/1.0.0/README.md create mode 100644 functions/jwt-parser/1.0.0/function.yaml diff --git a/functions/jwt-parser/1.0.0/OWNERS.md b/functions/jwt-parser/1.0.0/OWNERS.md new file mode 100644 index 0000000..ec02839 --- /dev/null +++ b/functions/jwt-parser/1.0.0/OWNERS.md @@ -0,0 +1,4 @@ +# OWNERS + +* @gabriel-farache +* @ishan diff --git a/functions/jwt-parser/1.0.0/README.md b/functions/jwt-parser/1.0.0/README.md new file mode 100644 index 0000000..0f00296 --- /dev/null +++ b/functions/jwt-parser/1.0.0/README.md @@ -0,0 +1,194 @@ +# JWT Parser Function + +The JWT Parser function allows SonataFlow workflows to parse and extract information from JWT (JSON Web Token) tokens. This is particularly useful for accessing user information and claims from authentication tokens passed in workflow headers. + +## Overview + +This function provides three main operations: +- **parse**: Extract the complete JWT payload as a JSON object +- **extractUser**: Extract standard user information from JWT claims (sub, preferred_username, email, etc.) +- **extractClaim**: Extract a specific claim by name + +## Usage + +### Basic JWT Parsing + +```json +{ + "functions": [ + { + "name": "parseJWT", + "type": "custom", + "operation": "jwt-parser" + } + ], + "states": [ + { + "name": "parseToken", + "type": "operation", + "actions": [ + { + "name": "parseAction", + "functionRef": { + "refName": "parseJWT", + "arguments": { + "token": "${ $WORKFLOW.headers.\"Authorization\" }", + "operation": "parse" + } + } + } + ] + } + ] +} +``` + +### Extract User Information + +```json +{ + "functions": [ + { + "name": "extractUser", + "type": "custom", + "operation": "jwt-parser:extractUser" + } + ], + "states": [ + { + "name": "extractUserName", + "type": "operation", + "actions": [ + { + "name": "extractUserAction", + "functionRef": { + "refName": "extractUser", + "arguments": { + "token": "${ $WORKFLOW.headers.\"X-Authorization-acme_financial_auth\" }" + } + } + } + ], + "stateDataFilter": { + "output": "${ { user: .result.preferred_username } }" + } + } + ] +} +``` + +### Extract Specific Claim + +```json +{ + "functions": [ + { + "name": "extractClaim", + "type": "custom", + "operation": "jwt-parser:extractClaim" + } + ], + "states": [ + { + "name": "extractRole", + "type": "operation", + "actions": [ + { + "name": "extractRoleAction", + "functionRef": { + "refName": "extractClaim", + "arguments": { + "token": "${ $WORKFLOW.headers.\"Authorization\" }", + "claim": "role" + } + } + } + ] + } + ] +} +``` + +## Complete Example + +Here's a complete workflow that demonstrates JWT parsing for user personalization: + +```json +{ + "id": "jwt_example", + "version": "1.0", + "name": "JWT Token Processing Example", + "start": "extractUser", + "functions": [ + { + "name": "extractUser", + "type": "custom", + "operation": "jwt-parser:extractUser" + } + ], + "states": [ + { + "name": "extractUser", + "type": "operation", + "actions": [ + { + "name": "extractUserAction", + "functionRef": { + "refName": "extractUser", + "arguments": { + "token": "${ $WORKFLOW.headers.\"X-Authorization-acme_financial_auth\" }" + } + } + } + ], + "stateDataFilter": { + "output": "${ { user: .result.preferred_username } }" + }, + "transition": "personalizedResponse" + }, + { + "name": "personalizedResponse", + "type": "inject", + "data": { + "approved": true + }, + "stateDataFilter": { + "output": "${ { message: \"Congrats \\(.user)! Your request has been approved!\", approved } }" + }, + "end": true + } + ] +} +``` + +## Parameters + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `token` | string | Yes | The JWT token to parse (Bearer prefix will be automatically removed) | +| `operation` | string | No | Operation to perform: "parse", "extractUser", or "extractClaim" (default: "parse") | +| `claim` | string | No | Name of specific claim to extract (required when operation is "extractClaim") | + +## Output + +The function returns a JSON object containing: +- For `parse`: Complete JWT payload +- For `extractUser`: Standard user claims (sub, preferred_username, email, name, etc.) +- For `extractClaim`: The specific claim value + +## Token Format Support + +The function supports JWT tokens in various formats: +- Raw JWT token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` +- Bearer token: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` + +## Error Handling + +The function will fail if: +- Token is null or empty +- Token format is invalid +- Required claim parameter is missing for extractClaim operation + +## Security Note + +This function extracts claims from JWT tokens without signature verification. In production environments, ensure proper token validation is performed at the API gateway or authentication layer before tokens reach the workflow. diff --git a/functions/jwt-parser/1.0.0/function.yaml b/functions/jwt-parser/1.0.0/function.yaml new file mode 100644 index 0000000..7614b0e --- /dev/null +++ b/functions/jwt-parser/1.0.0/function.yaml @@ -0,0 +1,30 @@ +input: + schema: + document: + type: object + properties: + token: + type: string + title: JWT Token + description: The JWT token to parse (can include "Bearer " prefix) + operation: + type: string + enum: [ "parse", "extractUser", "extractClaim" ] + default: "parse" + title: Operation + description: The operation to perform on the JWT token + claim: + type: string + title: Claim Name + description: The name of the specific claim to extract (required for extractClaim operation) + required: [ token ] +output: + schema: + document: + type: object + description: The parsed JWT payload or extracted claim as a JSON object +call: jwt-parser +with: + operation: ${ .operation } + token: ${ .token } + claim: ${ .claim } From a656bb5c560e14f2ccb175a11dcd158af6a25304 Mon Sep 17 00:00:00 2001 From: Ishan Jogi Date: Wed, 13 Aug 2025 16:00:28 +0530 Subject: [PATCH 2/6] 1. **Use Serverless Workflow 1.x syntax** with proper YAML format and `dsl: 1.0.0-alpha1` 2. **Replace the recursive call** with a proper `set` task using jq expressions 3. **Implement JWT decoding with jq**: The function now uses `split(".")[1] | @base64d | fromjson` to decode the JWT payload 4. **Add optional claim extraction** via `claimPath` parameter using jq path navigation Signed-off-by: Ishan Jogi --- functions/jwt-parser/1.0.0/README.md | 263 +++++++++-------------- functions/jwt-parser/1.0.0/function.yaml | 31 +-- 2 files changed, 123 insertions(+), 171 deletions(-) diff --git a/functions/jwt-parser/1.0.0/README.md b/functions/jwt-parser/1.0.0/README.md index 0f00296..df50877 100644 --- a/functions/jwt-parser/1.0.0/README.md +++ b/functions/jwt-parser/1.0.0/README.md @@ -1,180 +1,109 @@ # JWT Parser Function -The JWT Parser function allows SonataFlow workflows to parse and extract information from JWT (JSON Web Token) tokens. This is particularly useful for accessing user information and claims from authentication tokens passed in workflow headers. +The JWT Parser function allows Serverless Workflow 1.x workflows to parse and extract information from JWT (JSON Web Token) tokens using jq expressions. This function decodes the JWT payload and optionally extracts specific claims. ## Overview -This function provides three main operations: -- **parse**: Extract the complete JWT payload as a JSON object -- **extractUser**: Extract standard user information from JWT claims (sub, preferred_username, email, etc.) -- **extractClaim**: Extract a specific claim by name +This function uses a `set` task with jq expressions to: +- Decode JWT tokens (with or without "Bearer " prefix) +- Extract the complete JWT payload as JSON +- Optionally extract specific claims using jq paths ## Usage -### Basic JWT Parsing - -```json -{ - "functions": [ - { - "name": "parseJWT", - "type": "custom", - "operation": "jwt-parser" - } - ], - "states": [ - { - "name": "parseToken", - "type": "operation", - "actions": [ - { - "name": "parseAction", - "functionRef": { - "refName": "parseJWT", - "arguments": { - "token": "${ $WORKFLOW.headers.\"Authorization\" }", - "operation": "parse" - } - } - } - ] - } - ] -} +### Basic JWT Parsing (Complete Payload) + +```yaml +document: + dsl: 1.0.0-alpha1 + namespace: examples + name: jwt-parsing + version: 1.0.0 +do: + - parseToken: + use: jwt-parser + with: + token: ${ .headers.authorization } ``` -### Extract User Information - -```json -{ - "functions": [ - { - "name": "extractUser", - "type": "custom", - "operation": "jwt-parser:extractUser" - } - ], - "states": [ - { - "name": "extractUserName", - "type": "operation", - "actions": [ - { - "name": "extractUserAction", - "functionRef": { - "refName": "extractUser", - "arguments": { - "token": "${ $WORKFLOW.headers.\"X-Authorization-acme_financial_auth\" }" - } - } - } - ], - "stateDataFilter": { - "output": "${ { user: .result.preferred_username } }" - } - } - ] -} +### Extract Specific Claims + +```yaml +document: + dsl: 1.0.0-alpha1 + namespace: examples + name: jwt-user-extraction + version: 1.0.0 +do: + - extractUsername: + use: jwt-parser + with: + token: ${ .headers.authorization } + claimPath: ".preferred_username" + - extractEmail: + use: jwt-parser + with: + token: ${ .headers.authorization } + claimPath: ".email" ``` -### Extract Specific Claim - -```json -{ - "functions": [ - { - "name": "extractClaim", - "type": "custom", - "operation": "jwt-parser:extractClaim" - } - ], - "states": [ - { - "name": "extractRole", - "type": "operation", - "actions": [ - { - "name": "extractRoleAction", - "functionRef": { - "refName": "extractClaim", - "arguments": { - "token": "${ $WORKFLOW.headers.\"Authorization\" }", - "claim": "role" - } - } - } - ] - } - ] -} +### Multiple Claim Extraction + +```yaml +document: + dsl: 1.0.0-alpha1 + namespace: examples + name: jwt-multi-claims + version: 1.0.0 +do: + - getUserInfo: + use: jwt-parser + with: + token: ${ .headers["x-authorization-acme_financial_auth"] } + - processUserData: + use: set + set: + username: ${ .result.preferred_username } + email: ${ .result.email } + userId: ${ .result.sub } + message: ${ "Welcome " + .username + "! Your request has been processed." } ``` -## Complete Example - -Here's a complete workflow that demonstrates JWT parsing for user personalization: - -```json -{ - "id": "jwt_example", - "version": "1.0", - "name": "JWT Token Processing Example", - "start": "extractUser", - "functions": [ - { - "name": "extractUser", - "type": "custom", - "operation": "jwt-parser:extractUser" - } - ], - "states": [ - { - "name": "extractUser", - "type": "operation", - "actions": [ - { - "name": "extractUserAction", - "functionRef": { - "refName": "extractUser", - "arguments": { - "token": "${ $WORKFLOW.headers.\"X-Authorization-acme_financial_auth\" }" - } - } - } - ], - "stateDataFilter": { - "output": "${ { user: .result.preferred_username } }" - }, - "transition": "personalizedResponse" - }, - { - "name": "personalizedResponse", - "type": "inject", - "data": { - "approved": true - }, - "stateDataFilter": { - "output": "${ { message: \"Congrats \\(.user)! Your request has been approved!\", approved } }" - }, - "end": true - } - ] -} +## Complete Example - Loan Approval with User Personalization + +```yaml +document: + dsl: 1.0.0-alpha1 + namespace: examples + name: loan-approval-jwt + version: 1.0.0 +do: + - extractUserInfo: + use: jwt-parser + with: + token: ${ .headers["x-authorization-acme_financial_auth"] } + - processLoanApproval: + use: set + set: + user: ${ .result.preferred_username } + userId: ${ .result.sub } + email: ${ .result.email } + loanApproved: true + message: ${ "Congrats " + .user + "! Your loan has been approved!" } ``` ## Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `token` | string | Yes | The JWT token to parse (Bearer prefix will be automatically removed) | -| `operation` | string | No | Operation to perform: "parse", "extractUser", or "extractClaim" (default: "parse") | -| `claim` | string | No | Name of specific claim to extract (required when operation is "extractClaim") | +| `token` | string | Yes | The JWT token to parse (Bearer prefix will be automatically handled) | +| `claimPath` | string | No | jq path to extract specific claim (e.g., ".sub", ".preferred_username", ".email") | ## Output -The function returns a JSON object containing: -- For `parse`: Complete JWT payload -- For `extractUser`: Standard user claims (sub, preferred_username, email, name, etc.) -- For `extractClaim`: The specific claim value +The function returns: +- `claims`: The complete decoded JWT payload as JSON object +- `result`: Either the complete payload (if no claimPath) or the specific claim value (if claimPath provided) ## Token Format Support @@ -182,12 +111,32 @@ The function supports JWT tokens in various formats: - Raw JWT token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` - Bearer token: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` +## jq Expression Details + +The function uses these jq expressions: +- **Token cleanup**: Removes "Bearer " prefix if present +- **JWT decoding**: Splits token, extracts payload (part 1), base64 decodes, and parses JSON +- **Claim extraction**: Uses jq path navigation to extract specific claims + +## Common Claim Paths + +- `.sub` - Subject (user ID) +- `.preferred_username` - Username +- `.email` - Email address +- `.name` - Full name +- `.given_name` - First name +- `.family_name` - Last name +- `.roles` - User roles array +- `.exp` - Expiration timestamp +- `.iat` - Issued at timestamp + ## Error Handling -The function will fail if: +The jq expressions will fail if: - Token is null or empty -- Token format is invalid -- Required claim parameter is missing for extractClaim operation +- Token format is invalid (not 3 parts separated by dots) +- JWT payload is not valid base64 or JSON +- Specified claimPath does not exist ## Security Note diff --git a/functions/jwt-parser/1.0.0/function.yaml b/functions/jwt-parser/1.0.0/function.yaml index 7614b0e..1ee0184 100644 --- a/functions/jwt-parser/1.0.0/function.yaml +++ b/functions/jwt-parser/1.0.0/function.yaml @@ -7,24 +7,27 @@ input: type: string title: JWT Token description: The JWT token to parse (can include "Bearer " prefix) - operation: + claimPath: type: string - enum: [ "parse", "extractUser", "extractClaim" ] - default: "parse" - title: Operation - description: The operation to perform on the JWT token - claim: - type: string - title: Claim Name - description: The name of the specific claim to extract (required for extractClaim operation) + title: Claim Path + description: Optional jq path to extract specific claim (e.g., ".sub", ".preferred_username", ".email") required: [ token ] output: schema: document: type: object description: The parsed JWT payload or extracted claim as a JSON object -call: jwt-parser -with: - operation: ${ .operation } - token: ${ .token } - claim: ${ .claim } +use: set +set: + claims: >- + if (.token | startswith("Bearer ")) then + (.token[7:] | split(".")[1] | @base64d | fromjson) + else + (.token | split(".")[1] | @base64d | fromjson) + end + result: >- + if .claimPath then + .claims | getpath(.claimPath | split(".") | map(select(. != ""))) + else + .claims + end From f3fc9940a19c94749199e62136aed6fba92ff2ce Mon Sep 17 00:00:00 2001 From: Ishan Jogi Date: Wed, 13 Aug 2025 16:14:41 +0530 Subject: [PATCH 3/6] updated Readme.md file Signed-off-by: Ishan Jogi --- functions/jwt-parser/1.0.0/README.md | 189 +++++++++++++++------------ 1 file changed, 104 insertions(+), 85 deletions(-) diff --git a/functions/jwt-parser/1.0.0/README.md b/functions/jwt-parser/1.0.0/README.md index df50877..8812a54 100644 --- a/functions/jwt-parser/1.0.0/README.md +++ b/functions/jwt-parser/1.0.0/README.md @@ -1,143 +1,162 @@ # JWT Parser Function -The JWT Parser function allows Serverless Workflow 1.x workflows to parse and extract information from JWT (JSON Web Token) tokens using jq expressions. This function decodes the JWT payload and optionally extracts specific claims. +A Serverless Workflow 1.x function that decodes JWT (JSON Web Token) payloads using jq expressions. This function provides pure jq-based JWT parsing without external dependencies. -## Overview +## Technical Implementation -This function uses a `set` task with jq expressions to: -- Decode JWT tokens (with or without "Bearer " prefix) -- Extract the complete JWT payload as JSON -- Optionally extract specific claims using jq paths +This function implements JWT payload extraction through a `set` task using jq expressions: + +**Core JWT Decoding Logic:** +```jq +if (.token | startswith("Bearer ")) then + (.token[7:] | split(".")[1] | @base64d | fromjson) +else + (.token | split(".")[1] | @base64d | fromjson) +end +``` + +**Technical Steps:** +1. **Prefix Handling**: Detects and removes "Bearer " prefix if present +2. **Token Splitting**: Splits JWT on "." to isolate header, payload, signature +3. **Payload Extraction**: Selects the middle part (index 1) containing claims +4. **Base64 Decoding**: Uses `@base64d` to decode the base64url payload +5. **JSON Parsing**: Converts decoded string to JSON object with `fromjson` +6. **Optional Claim Navigation**: Uses jq path expressions for specific claim extraction ## Usage -### Basic JWT Parsing (Complete Payload) +### Complete Payload Extraction ```yaml document: dsl: 1.0.0-alpha1 - namespace: examples - name: jwt-parsing + namespace: technical + name: jwt-full-decode version: 1.0.0 do: - - parseToken: + - decodeJWT: use: jwt-parser with: token: ${ .headers.authorization } + # Returns: { claims: {...}, result: {...} } ``` -### Extract Specific Claims +### Specific Claim Extraction ```yaml document: dsl: 1.0.0-alpha1 - namespace: examples - name: jwt-user-extraction + namespace: technical + name: jwt-claim-extraction version: 1.0.0 do: - - extractUsername: + - extractSubject: use: jwt-parser with: token: ${ .headers.authorization } - claimPath: ".preferred_username" - - extractEmail: + claimPath: ".sub" + # Returns: { claims: {...}, result: "user-id-123" } + - extractNestedClaim: use: jwt-parser with: token: ${ .headers.authorization } - claimPath: ".email" + claimPath: ".custom.department" + # Returns: { claims: {...}, result: "engineering" } ``` -### Multiple Claim Extraction +### Token Format Handling ```yaml document: dsl: 1.0.0-alpha1 - namespace: examples - name: jwt-multi-claims + namespace: technical + name: jwt-format-handling version: 1.0.0 do: - - getUserInfo: + - parseRawToken: use: jwt-parser with: - token: ${ .headers["x-authorization-acme_financial_auth"] } - - processUserData: - use: set - set: - username: ${ .result.preferred_username } - email: ${ .result.email } - userId: ${ .result.sub } - message: ${ "Welcome " + .username + "! Your request has been processed." } -``` - -## Complete Example - Loan Approval with User Personalization - -```yaml -document: - dsl: 1.0.0-alpha1 - namespace: examples - name: loan-approval-jwt - version: 1.0.0 -do: - - extractUserInfo: + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" + - parseBearerToken: use: jwt-parser with: - token: ${ .headers["x-authorization-acme_financial_auth"] } - - processLoanApproval: - use: set - set: - user: ${ .result.preferred_username } - userId: ${ .result.sub } - email: ${ .result.email } - loanApproved: true - message: ${ "Congrats " + .user + "! Your loan has been approved!" } + token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" + # Both handle the token format automatically ``` -## Parameters +## Function Specification +### Input Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| -| `token` | string | Yes | The JWT token to parse (Bearer prefix will be automatically handled) | -| `claimPath` | string | No | jq path to extract specific claim (e.g., ".sub", ".preferred_username", ".email") | - -## Output +| `token` | string | Yes | JWT token (raw or with "Bearer " prefix) | +| `claimPath` | string | No | jq path expression for specific claim extraction | + +### Output Structure +```json +{ + "claims": { + "sub": "user-123", + "preferred_username": "john.doe", + "email": "john@example.com", + "exp": 1234567890, + "iat": 1234567800 + }, + "result": "..." // Complete payload or specific claim based on claimPath +} +``` -The function returns: -- `claims`: The complete decoded JWT payload as JSON object -- `result`: Either the complete payload (if no claimPath) or the specific claim value (if claimPath provided) +## Technical Details -## Token Format Support +### JWT Token Structure +``` +header.payload.signature +``` +The function processes the **payload** section (index 1 after splitting on ".") -The function supports JWT tokens in various formats: -- Raw JWT token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` -- Bearer token: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` +### jq Expression Breakdown -## jq Expression Details +**Primary Expression:** +```jq +if (.token | startswith("Bearer ")) then + (.token[7:] | split(".")[1] | @base64d | fromjson) +else + (.token | split(".")[1] | @base64d | fromjson) +end +``` -The function uses these jq expressions: -- **Token cleanup**: Removes "Bearer " prefix if present -- **JWT decoding**: Splits token, extracts payload (part 1), base64 decodes, and parses JSON -- **Claim extraction**: Uses jq path navigation to extract specific claims +**Claim Path Expression:** +```jq +if .claimPath then + .claims | getpath(.claimPath | split(".") | map(select(. != ""))) +else + .claims +end +``` -## Common Claim Paths +### Supported Token Formats +- **Raw JWT**: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.sig` +- **Bearer Format**: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.sig` -- `.sub` - Subject (user ID) -- `.preferred_username` - Username -- `.email` - Email address -- `.name` - Full name -- `.given_name` - First name -- `.family_name` - Last name -- `.roles` - User roles array -- `.exp` - Expiration timestamp -- `.iat` - Issued at timestamp +### Claim Path Examples +| Path | Description | Example Result | +|------|-------------|----------------| +| `.sub` | Subject identifier | `"user-123"` | +| `.preferred_username` | Username | `"john.doe"` | +| `.custom.department` | Nested custom claim | `"engineering"` | +| `.roles[0]` | First role in array | `"admin"` | -## Error Handling +## Error Conditions -The jq expressions will fail if: -- Token is null or empty -- Token format is invalid (not 3 parts separated by dots) -- JWT payload is not valid base64 or JSON -- Specified claimPath does not exist +The function will fail with jq errors if: +- **Invalid token format**: Not exactly 3 parts separated by dots +- **Invalid base64**: Payload section cannot be base64 decoded +- **Invalid JSON**: Decoded payload is not valid JSON +- **Invalid claim path**: Specified jq path does not exist in payload -## Security Note +## Implementation Notes -This function extracts claims from JWT tokens without signature verification. In production environments, ensure proper token validation is performed at the API gateway or authentication layer before tokens reach the workflow. +- **No signature verification**: Function extracts claims without cryptographic validation +- **Base64URL decoding**: Uses jq's `@base64d` which handles base64url format +- **Path navigation**: Uses jq's `getpath()` for safe claim extraction +- **Prefix handling**: Automatically detects and strips "Bearer " prefix From 8b03f44bb25485ae71d9cea0f99d836bba382a14 Mon Sep 17 00:00:00 2001 From: Ishan Jogi Date: Wed, 13 Aug 2025 16:39:53 +0530 Subject: [PATCH 4/6] covered edge case scenarios Signed-off-by: Ishan Jogi --- functions/jwt-parser/1.0.0/README.md | 72 +++++++++++++++++------- functions/jwt-parser/1.0.0/function.yaml | 22 +++++--- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/functions/jwt-parser/1.0.0/README.md b/functions/jwt-parser/1.0.0/README.md index 8812a54..3519e90 100644 --- a/functions/jwt-parser/1.0.0/README.md +++ b/functions/jwt-parser/1.0.0/README.md @@ -8,20 +8,21 @@ This function implements JWT payload extraction through a `set` task using jq ex **Core JWT Decoding Logic:** ```jq -if (.token | startswith("Bearer ")) then - (.token[7:] | split(".")[1] | @base64d | fromjson) -else - (.token | split(".")[1] | @base64d | fromjson) -end +(if (.token | startswith("Bearer ")) then .token[7:] else .token end) | +split(".") | +if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | +@base64d | +fromjson ``` **Technical Steps:** 1. **Prefix Handling**: Detects and removes "Bearer " prefix if present 2. **Token Splitting**: Splits JWT on "." to isolate header, payload, signature -3. **Payload Extraction**: Selects the middle part (index 1) containing claims -4. **Base64 Decoding**: Uses `@base64d` to decode the base64url payload -5. **JSON Parsing**: Converts decoded string to JSON object with `fromjson` -6. **Optional Claim Navigation**: Uses jq path expressions for specific claim extraction +3. **Format Validation**: Ensures exactly 3 parts (header.payload.signature) +4. **Payload Extraction**: Selects the middle part (index 1) containing claims +5. **Base64 Decoding**: Uses `@base64d` to decode the base64url payload +6. **JSON Parsing**: Converts decoded string to JSON object with `fromjson` +7. **Optional Claim Navigation**: Uses jq path expressions for specific claim extraction ## Usage @@ -116,21 +117,27 @@ The function processes the **payload** section (index 1 after splitting on ".") ### jq Expression Breakdown -**Primary Expression:** +**JWT Decoding Expression:** ```jq -if (.token | startswith("Bearer ")) then - (.token[7:] | split(".")[1] | @base64d | fromjson) -else - (.token | split(".")[1] | @base64d | fromjson) -end +(if (.token | startswith("Bearer ")) then .token[7:] else .token end) | +split(".") | +if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | +@base64d | +fromjson ``` **Claim Path Expression:** ```jq -if .claimPath then - .claims | getpath(.claimPath | split(".") | map(select(. != ""))) +((if (.token | startswith("Bearer ")) then .token[7:] else .token end) | +split(".") | +if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | +@base64d | +fromjson) as $decoded | +if (.claimPath // null) != null then + (.claimPath | split(".") | map(select(. != ""))) as $path | + $decoded | getpath($path) else - .claims + $decoded end ``` @@ -157,6 +164,29 @@ The function will fail with jq errors if: ## Implementation Notes - **No signature verification**: Function extracts claims without cryptographic validation -- **Base64URL decoding**: Uses jq's `@base64d` which handles base64url format -- **Path navigation**: Uses jq's `getpath()` for safe claim extraction -- **Prefix handling**: Automatically detects and strips "Bearer " prefix +- **Base64URL decoding**: Uses jq's `@base64d` which handles base64url format +- **Path navigation**: Uses `getpath()` with string splitting for safe claim extraction +- **Prefix handling**: Conditional logic automatically detects and strips "Bearer " prefix +- **Pipeline processing**: Uses jq pipe operators for clean data transformation flow +- **Null safety**: Uses `has("claimPath")` to check for optional parameter presence + +## Technical Validation + +### JWT Structure Validation +The function expects standard JWT format: `header.payload.signature` +- **Header**: Algorithm and token type (ignored) +- **Payload**: Base64URL-encoded JSON claims (processed) +- **Signature**: Cryptographic signature (ignored) + +### Base64URL Decoding +JWT uses Base64URL encoding (RFC 4648 Section 5): +- Uses `-` and `_` instead of `+` and `/` +- No padding characters required +- jq's `@base64d` handles this automatically + +### Claim Path Syntax +Claim paths follow jq object navigation: +- `.sub` - Direct property access +- `.custom.department` - Nested object access +- `.roles[0]` - Array element access +- Path components split on `.` and filtered for non-empty strings diff --git a/functions/jwt-parser/1.0.0/function.yaml b/functions/jwt-parser/1.0.0/function.yaml index 1ee0184..6582683 100644 --- a/functions/jwt-parser/1.0.0/function.yaml +++ b/functions/jwt-parser/1.0.0/function.yaml @@ -20,14 +20,20 @@ output: use: set set: claims: >- - if (.token | startswith("Bearer ")) then - (.token[7:] | split(".")[1] | @base64d | fromjson) - else - (.token | split(".")[1] | @base64d | fromjson) - end + (if (.token | startswith("Bearer ")) then .token[7:] else .token end) | + split(".") | + if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | + @base64d | + fromjson result: >- - if .claimPath then - .claims | getpath(.claimPath | split(".") | map(select(. != ""))) + ((if (.token | startswith("Bearer ")) then .token[7:] else .token end) | + split(".") | + if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | + @base64d | + fromjson) as $decoded | + if (.claimPath // null) != null then + (.claimPath | split(".") | map(select(. != ""))) as $path | + $decoded | getpath($path) else - .claims + $decoded end From 993778ac0385155aeb799584c0d940aea864c00f Mon Sep 17 00:00:00 2001 From: Ishan Jogi Date: Wed, 13 Aug 2025 17:59:05 +0530 Subject: [PATCH 5/6] updated owners Signed-off-by: Ishan Jogi Signed-off-by: Ishan Jogi --- functions/jwt-parser/1.0.0/OWNERS.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/functions/jwt-parser/1.0.0/OWNERS.md b/functions/jwt-parser/1.0.0/OWNERS.md index ec02839..85c1fac 100644 --- a/functions/jwt-parser/1.0.0/OWNERS.md +++ b/functions/jwt-parser/1.0.0/OWNERS.md @@ -1,4 +1,2 @@ # OWNERS - -* @gabriel-farache -* @ishan +* @ishanjogi89 From ad8c9fae831a0e17ecf6d3261137d4a40a113729 Mon Sep 17 00:00:00 2001 From: Ishan Jogi Date: Mon, 25 Aug 2025 11:12:01 +0530 Subject: [PATCH 6/6] fixed issue as per review comments Signed-off-by: Ishan Jogi --- functions/jwt-parser/1.0.0/README.md | 42 ++++++++++++++---------- functions/jwt-parser/1.0.0/function.yaml | 37 +++++++++------------ 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/functions/jwt-parser/1.0.0/README.md b/functions/jwt-parser/1.0.0/README.md index 3519e90..eb5b1d9 100644 --- a/functions/jwt-parser/1.0.0/README.md +++ b/functions/jwt-parser/1.0.0/README.md @@ -4,7 +4,7 @@ A Serverless Workflow 1.x function that decodes JWT (JSON Web Token) payloads us ## Technical Implementation -This function implements JWT payload extraction through a `set` task using jq expressions: +This function implements JWT payload extraction through a `run` task using jq expressions: **Core JWT Decoding Logic:** ```jq @@ -36,10 +36,10 @@ document: version: 1.0.0 do: - decodeJWT: - use: jwt-parser + call: jwt-parser with: token: ${ .headers.authorization } - # Returns: { claims: {...}, result: {...} } + # Returns: complete JWT payload as JSON object ``` ### Specific Claim Extraction @@ -52,17 +52,17 @@ document: version: 1.0.0 do: - extractSubject: - use: jwt-parser + call: jwt-parser with: token: ${ .headers.authorization } claimPath: ".sub" - # Returns: { claims: {...}, result: "user-id-123" } + # Returns: "user-id-123" - extractNestedClaim: - use: jwt-parser + call: jwt-parser with: token: ${ .headers.authorization } claimPath: ".custom.department" - # Returns: { claims: {...}, result: "engineering" } + # Returns: "engineering" ``` ### Token Format Handling @@ -75,11 +75,11 @@ document: version: 1.0.0 do: - parseRawToken: - use: jwt-parser + call: jwt-parser with: token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" - parseBearerToken: - use: jwt-parser + call: jwt-parser with: token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" # Both handle the token format automatically @@ -94,19 +94,27 @@ do: | `claimPath` | string | No | jq path expression for specific claim extraction | ### Output Structure + +**When `claimPath` is NOT provided** (complete payload): ```json { - "claims": { - "sub": "user-123", - "preferred_username": "john.doe", - "email": "john@example.com", - "exp": 1234567890, - "iat": 1234567800 - }, - "result": "..." // Complete payload or specific claim based on claimPath + "sub": "user-123", + "preferred_username": "john.doe", + "email": "john@example.com", + "exp": 1234567890, + "iat": 1234567800 } ``` +**When `claimPath` is provided** (specific claim value): +```json +"user-123" +``` +or +```json +"engineering" +``` + ## Technical Details ### JWT Token Structure diff --git a/functions/jwt-parser/1.0.0/function.yaml b/functions/jwt-parser/1.0.0/function.yaml index 6582683..452747d 100644 --- a/functions/jwt-parser/1.0.0/function.yaml +++ b/functions/jwt-parser/1.0.0/function.yaml @@ -15,25 +15,18 @@ input: output: schema: document: - type: object - description: The parsed JWT payload or extracted claim as a JSON object -use: set -set: - claims: >- - (if (.token | startswith("Bearer ")) then .token[7:] else .token end) | - split(".") | - if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | - @base64d | - fromjson - result: >- - ((if (.token | startswith("Bearer ")) then .token[7:] else .token end) | - split(".") | - if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | - @base64d | - fromjson) as $decoded | - if (.claimPath // null) != null then - (.claimPath | split(".") | map(select(. != ""))) as $path | - $decoded | getpath($path) - else - $decoded - end + description: The parsed JWT payload (when claimPath not provided) or the specific claim value (when claimPath provided) +run: + set: + result: >- + ((if (.token | startswith("Bearer ")) then .token[7:] else .token end) | + split(".") | + if length != 3 then error("Invalid JWT format: must have 3 parts") else .[1] end | + @base64d | + fromjson) as $decoded | + if (.claimPath // null) != null then + (.claimPath | split(".") | map(select(. != ""))) as $path | + $decoded | getpath($path) + else + $decoded + end