|
1 | 1 | # JWT Parser Function |
2 | 2 |
|
3 | | -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. |
| 3 | +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. |
4 | 4 |
|
5 | | -## Overview |
| 5 | +## Technical Implementation |
6 | 6 |
|
7 | | -This function uses a `set` task with jq expressions to: |
8 | | -- Decode JWT tokens (with or without "Bearer " prefix) |
9 | | -- Extract the complete JWT payload as JSON |
10 | | -- Optionally extract specific claims using jq paths |
| 7 | +This function implements JWT payload extraction through a `set` task using jq expressions: |
| 8 | + |
| 9 | +**Core JWT Decoding Logic:** |
| 10 | +```jq |
| 11 | +if (.token | startswith("Bearer ")) then |
| 12 | + (.token[7:] | split(".")[1] | @base64d | fromjson) |
| 13 | +else |
| 14 | + (.token | split(".")[1] | @base64d | fromjson) |
| 15 | +end |
| 16 | +``` |
| 17 | + |
| 18 | +**Technical Steps:** |
| 19 | +1. **Prefix Handling**: Detects and removes "Bearer " prefix if present |
| 20 | +2. **Token Splitting**: Splits JWT on "." to isolate header, payload, signature |
| 21 | +3. **Payload Extraction**: Selects the middle part (index 1) containing claims |
| 22 | +4. **Base64 Decoding**: Uses `@base64d` to decode the base64url payload |
| 23 | +5. **JSON Parsing**: Converts decoded string to JSON object with `fromjson` |
| 24 | +6. **Optional Claim Navigation**: Uses jq path expressions for specific claim extraction |
11 | 25 |
|
12 | 26 | ## Usage |
13 | 27 |
|
14 | | -### Basic JWT Parsing (Complete Payload) |
| 28 | +### Complete Payload Extraction |
15 | 29 |
|
16 | 30 | ```yaml |
17 | 31 | document: |
18 | 32 | dsl: 1.0.0-alpha1 |
19 | | - namespace: examples |
20 | | - name: jwt-parsing |
| 33 | + namespace: technical |
| 34 | + name: jwt-full-decode |
21 | 35 | version: 1.0.0 |
22 | 36 | do: |
23 | | - - parseToken: |
| 37 | + - decodeJWT: |
24 | 38 | use: jwt-parser |
25 | 39 | with: |
26 | 40 | token: ${ .headers.authorization } |
| 41 | + # Returns: { claims: {...}, result: {...} } |
27 | 42 | ``` |
28 | 43 |
|
29 | | -### Extract Specific Claims |
| 44 | +### Specific Claim Extraction |
30 | 45 |
|
31 | 46 | ```yaml |
32 | 47 | document: |
33 | 48 | dsl: 1.0.0-alpha1 |
34 | | - namespace: examples |
35 | | - name: jwt-user-extraction |
| 49 | + namespace: technical |
| 50 | + name: jwt-claim-extraction |
36 | 51 | version: 1.0.0 |
37 | 52 | do: |
38 | | - - extractUsername: |
| 53 | + - extractSubject: |
39 | 54 | use: jwt-parser |
40 | 55 | with: |
41 | 56 | token: ${ .headers.authorization } |
42 | | - claimPath: ".preferred_username" |
43 | | - - extractEmail: |
| 57 | + claimPath: ".sub" |
| 58 | + # Returns: { claims: {...}, result: "user-id-123" } |
| 59 | + - extractNestedClaim: |
44 | 60 | use: jwt-parser |
45 | 61 | with: |
46 | 62 | token: ${ .headers.authorization } |
47 | | - claimPath: ".email" |
| 63 | + claimPath: ".custom.department" |
| 64 | + # Returns: { claims: {...}, result: "engineering" } |
48 | 65 | ``` |
49 | 66 |
|
50 | | -### Multiple Claim Extraction |
| 67 | +### Token Format Handling |
51 | 68 |
|
52 | 69 | ```yaml |
53 | 70 | document: |
54 | 71 | dsl: 1.0.0-alpha1 |
55 | | - namespace: examples |
56 | | - name: jwt-multi-claims |
| 72 | + namespace: technical |
| 73 | + name: jwt-format-handling |
57 | 74 | version: 1.0.0 |
58 | 75 | do: |
59 | | - - getUserInfo: |
| 76 | + - parseRawToken: |
60 | 77 | use: jwt-parser |
61 | 78 | with: |
62 | | - token: ${ .headers["x-authorization-acme_financial_auth"] } |
63 | | - - processUserData: |
64 | | - use: set |
65 | | - set: |
66 | | - username: ${ .result.preferred_username } |
67 | | - email: ${ .result.email } |
68 | | - userId: ${ .result.sub } |
69 | | - message: ${ "Welcome " + .username + "! Your request has been processed." } |
70 | | -``` |
71 | | -
|
72 | | -## Complete Example - Loan Approval with User Personalization |
73 | | -
|
74 | | -```yaml |
75 | | -document: |
76 | | - dsl: 1.0.0-alpha1 |
77 | | - namespace: examples |
78 | | - name: loan-approval-jwt |
79 | | - version: 1.0.0 |
80 | | -do: |
81 | | - - extractUserInfo: |
| 79 | + token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" |
| 80 | + - parseBearerToken: |
82 | 81 | use: jwt-parser |
83 | 82 | with: |
84 | | - token: ${ .headers["x-authorization-acme_financial_auth"] } |
85 | | - - processLoanApproval: |
86 | | - use: set |
87 | | - set: |
88 | | - user: ${ .result.preferred_username } |
89 | | - userId: ${ .result.sub } |
90 | | - email: ${ .result.email } |
91 | | - loanApproved: true |
92 | | - message: ${ "Congrats " + .user + "! Your loan has been approved!" } |
| 83 | + token: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" |
| 84 | + # Both handle the token format automatically |
93 | 85 | ``` |
94 | 86 |
|
95 | | -## Parameters |
| 87 | +## Function Specification |
96 | 88 |
|
| 89 | +### Input Parameters |
97 | 90 | | Parameter | Type | Required | Description | |
98 | 91 | |-----------|------|----------|-------------| |
99 | | -| `token` | string | Yes | The JWT token to parse (Bearer prefix will be automatically handled) | |
100 | | -| `claimPath` | string | No | jq path to extract specific claim (e.g., ".sub", ".preferred_username", ".email") | |
101 | | - |
102 | | -## Output |
| 92 | +| `token` | string | Yes | JWT token (raw or with "Bearer " prefix) | |
| 93 | +| `claimPath` | string | No | jq path expression for specific claim extraction | |
| 94 | + |
| 95 | +### Output Structure |
| 96 | +```json |
| 97 | +{ |
| 98 | + "claims": { |
| 99 | + "sub": "user-123", |
| 100 | + "preferred_username": "john.doe", |
| 101 | + "email": "john@example.com", |
| 102 | + "exp": 1234567890, |
| 103 | + "iat": 1234567800 |
| 104 | + }, |
| 105 | + "result": "..." // Complete payload or specific claim based on claimPath |
| 106 | +} |
| 107 | +``` |
103 | 108 |
|
104 | | -The function returns: |
105 | | -- `claims`: The complete decoded JWT payload as JSON object |
106 | | -- `result`: Either the complete payload (if no claimPath) or the specific claim value (if claimPath provided) |
| 109 | +## Technical Details |
107 | 110 |
|
108 | | -## Token Format Support |
| 111 | +### JWT Token Structure |
| 112 | +``` |
| 113 | +header.payload.signature |
| 114 | +``` |
| 115 | +The function processes the **payload** section (index 1 after splitting on ".") |
109 | 116 |
|
110 | | -The function supports JWT tokens in various formats: |
111 | | -- Raw JWT token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` |
112 | | -- Bearer token: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` |
| 117 | +### jq Expression Breakdown |
113 | 118 |
|
114 | | -## jq Expression Details |
| 119 | +**Primary Expression:** |
| 120 | +```jq |
| 121 | +if (.token | startswith("Bearer ")) then |
| 122 | + (.token[7:] | split(".")[1] | @base64d | fromjson) |
| 123 | +else |
| 124 | + (.token | split(".")[1] | @base64d | fromjson) |
| 125 | +end |
| 126 | +``` |
115 | 127 |
|
116 | | -The function uses these jq expressions: |
117 | | -- **Token cleanup**: Removes "Bearer " prefix if present |
118 | | -- **JWT decoding**: Splits token, extracts payload (part 1), base64 decodes, and parses JSON |
119 | | -- **Claim extraction**: Uses jq path navigation to extract specific claims |
| 128 | +**Claim Path Expression:** |
| 129 | +```jq |
| 130 | +if .claimPath then |
| 131 | + .claims | getpath(.claimPath | split(".") | map(select(. != ""))) |
| 132 | +else |
| 133 | + .claims |
| 134 | +end |
| 135 | +``` |
120 | 136 |
|
121 | | -## Common Claim Paths |
| 137 | +### Supported Token Formats |
| 138 | +- **Raw JWT**: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.sig` |
| 139 | +- **Bearer Format**: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.sig` |
122 | 140 |
|
123 | | -- `.sub` - Subject (user ID) |
124 | | -- `.preferred_username` - Username |
125 | | -- `.email` - Email address |
126 | | -- `.name` - Full name |
127 | | -- `.given_name` - First name |
128 | | -- `.family_name` - Last name |
129 | | -- `.roles` - User roles array |
130 | | -- `.exp` - Expiration timestamp |
131 | | -- `.iat` - Issued at timestamp |
| 141 | +### Claim Path Examples |
| 142 | +| Path | Description | Example Result | |
| 143 | +|------|-------------|----------------| |
| 144 | +| `.sub` | Subject identifier | `"user-123"` | |
| 145 | +| `.preferred_username` | Username | `"john.doe"` | |
| 146 | +| `.custom.department` | Nested custom claim | `"engineering"` | |
| 147 | +| `.roles[0]` | First role in array | `"admin"` | |
132 | 148 |
|
133 | | -## Error Handling |
| 149 | +## Error Conditions |
134 | 150 |
|
135 | | -The jq expressions will fail if: |
136 | | -- Token is null or empty |
137 | | -- Token format is invalid (not 3 parts separated by dots) |
138 | | -- JWT payload is not valid base64 or JSON |
139 | | -- Specified claimPath does not exist |
| 151 | +The function will fail with jq errors if: |
| 152 | +- **Invalid token format**: Not exactly 3 parts separated by dots |
| 153 | +- **Invalid base64**: Payload section cannot be base64 decoded |
| 154 | +- **Invalid JSON**: Decoded payload is not valid JSON |
| 155 | +- **Invalid claim path**: Specified jq path does not exist in payload |
140 | 156 |
|
141 | | -## Security Note |
| 157 | +## Implementation Notes |
142 | 158 |
|
143 | | -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. |
| 159 | +- **No signature verification**: Function extracts claims without cryptographic validation |
| 160 | +- **Base64URL decoding**: Uses jq's `@base64d` which handles base64url format |
| 161 | +- **Path navigation**: Uses jq's `getpath()` for safe claim extraction |
| 162 | +- **Prefix handling**: Automatically detects and strips "Bearer " prefix |
0 commit comments