|
1 | 1 | # JWT Parser Function |
2 | 2 |
|
3 | | -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. |
| 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. |
4 | 4 |
|
5 | 5 | ## Overview |
6 | 6 |
|
7 | | -This function provides three main operations: |
8 | | -- **parse**: Extract the complete JWT payload as a JSON object |
9 | | -- **extractUser**: Extract standard user information from JWT claims (sub, preferred_username, email, etc.) |
10 | | -- **extractClaim**: Extract a specific claim by name |
| 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 |
11 | 11 |
|
12 | 12 | ## Usage |
13 | 13 |
|
14 | | -### Basic JWT Parsing |
15 | | - |
16 | | -```json |
17 | | -{ |
18 | | - "functions": [ |
19 | | - { |
20 | | - "name": "parseJWT", |
21 | | - "type": "custom", |
22 | | - "operation": "jwt-parser" |
23 | | - } |
24 | | - ], |
25 | | - "states": [ |
26 | | - { |
27 | | - "name": "parseToken", |
28 | | - "type": "operation", |
29 | | - "actions": [ |
30 | | - { |
31 | | - "name": "parseAction", |
32 | | - "functionRef": { |
33 | | - "refName": "parseJWT", |
34 | | - "arguments": { |
35 | | - "token": "${ $WORKFLOW.headers.\"Authorization\" }", |
36 | | - "operation": "parse" |
37 | | - } |
38 | | - } |
39 | | - } |
40 | | - ] |
41 | | - } |
42 | | - ] |
43 | | -} |
| 14 | +### Basic JWT Parsing (Complete Payload) |
| 15 | + |
| 16 | +```yaml |
| 17 | +document: |
| 18 | + dsl: 1.0.0-alpha1 |
| 19 | + namespace: examples |
| 20 | + name: jwt-parsing |
| 21 | + version: 1.0.0 |
| 22 | +do: |
| 23 | + - parseToken: |
| 24 | + use: jwt-parser |
| 25 | + with: |
| 26 | + token: ${ .headers.authorization } |
44 | 27 | ``` |
45 | 28 |
|
46 | | -### Extract User Information |
47 | | - |
48 | | -```json |
49 | | -{ |
50 | | - "functions": [ |
51 | | - { |
52 | | - "name": "extractUser", |
53 | | - "type": "custom", |
54 | | - "operation": "jwt-parser:extractUser" |
55 | | - } |
56 | | - ], |
57 | | - "states": [ |
58 | | - { |
59 | | - "name": "extractUserName", |
60 | | - "type": "operation", |
61 | | - "actions": [ |
62 | | - { |
63 | | - "name": "extractUserAction", |
64 | | - "functionRef": { |
65 | | - "refName": "extractUser", |
66 | | - "arguments": { |
67 | | - "token": "${ $WORKFLOW.headers.\"X-Authorization-acme_financial_auth\" }" |
68 | | - } |
69 | | - } |
70 | | - } |
71 | | - ], |
72 | | - "stateDataFilter": { |
73 | | - "output": "${ { user: .result.preferred_username } }" |
74 | | - } |
75 | | - } |
76 | | - ] |
77 | | -} |
| 29 | +### Extract Specific Claims |
| 30 | +
|
| 31 | +```yaml |
| 32 | +document: |
| 33 | + dsl: 1.0.0-alpha1 |
| 34 | + namespace: examples |
| 35 | + name: jwt-user-extraction |
| 36 | + version: 1.0.0 |
| 37 | +do: |
| 38 | + - extractUsername: |
| 39 | + use: jwt-parser |
| 40 | + with: |
| 41 | + token: ${ .headers.authorization } |
| 42 | + claimPath: ".preferred_username" |
| 43 | + - extractEmail: |
| 44 | + use: jwt-parser |
| 45 | + with: |
| 46 | + token: ${ .headers.authorization } |
| 47 | + claimPath: ".email" |
78 | 48 | ``` |
79 | 49 |
|
80 | | -### Extract Specific Claim |
81 | | - |
82 | | -```json |
83 | | -{ |
84 | | - "functions": [ |
85 | | - { |
86 | | - "name": "extractClaim", |
87 | | - "type": "custom", |
88 | | - "operation": "jwt-parser:extractClaim" |
89 | | - } |
90 | | - ], |
91 | | - "states": [ |
92 | | - { |
93 | | - "name": "extractRole", |
94 | | - "type": "operation", |
95 | | - "actions": [ |
96 | | - { |
97 | | - "name": "extractRoleAction", |
98 | | - "functionRef": { |
99 | | - "refName": "extractClaim", |
100 | | - "arguments": { |
101 | | - "token": "${ $WORKFLOW.headers.\"Authorization\" }", |
102 | | - "claim": "role" |
103 | | - } |
104 | | - } |
105 | | - } |
106 | | - ] |
107 | | - } |
108 | | - ] |
109 | | -} |
| 50 | +### Multiple Claim Extraction |
| 51 | +
|
| 52 | +```yaml |
| 53 | +document: |
| 54 | + dsl: 1.0.0-alpha1 |
| 55 | + namespace: examples |
| 56 | + name: jwt-multi-claims |
| 57 | + version: 1.0.0 |
| 58 | +do: |
| 59 | + - getUserInfo: |
| 60 | + use: jwt-parser |
| 61 | + 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." } |
110 | 70 | ``` |
111 | 71 |
|
112 | | -## Complete Example |
113 | | - |
114 | | -Here's a complete workflow that demonstrates JWT parsing for user personalization: |
115 | | - |
116 | | -```json |
117 | | -{ |
118 | | - "id": "jwt_example", |
119 | | - "version": "1.0", |
120 | | - "name": "JWT Token Processing Example", |
121 | | - "start": "extractUser", |
122 | | - "functions": [ |
123 | | - { |
124 | | - "name": "extractUser", |
125 | | - "type": "custom", |
126 | | - "operation": "jwt-parser:extractUser" |
127 | | - } |
128 | | - ], |
129 | | - "states": [ |
130 | | - { |
131 | | - "name": "extractUser", |
132 | | - "type": "operation", |
133 | | - "actions": [ |
134 | | - { |
135 | | - "name": "extractUserAction", |
136 | | - "functionRef": { |
137 | | - "refName": "extractUser", |
138 | | - "arguments": { |
139 | | - "token": "${ $WORKFLOW.headers.\"X-Authorization-acme_financial_auth\" }" |
140 | | - } |
141 | | - } |
142 | | - } |
143 | | - ], |
144 | | - "stateDataFilter": { |
145 | | - "output": "${ { user: .result.preferred_username } }" |
146 | | - }, |
147 | | - "transition": "personalizedResponse" |
148 | | - }, |
149 | | - { |
150 | | - "name": "personalizedResponse", |
151 | | - "type": "inject", |
152 | | - "data": { |
153 | | - "approved": true |
154 | | - }, |
155 | | - "stateDataFilter": { |
156 | | - "output": "${ { message: \"Congrats \\(.user)! Your request has been approved!\", approved } }" |
157 | | - }, |
158 | | - "end": true |
159 | | - } |
160 | | - ] |
161 | | -} |
| 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: |
| 82 | + use: jwt-parser |
| 83 | + 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!" } |
162 | 93 | ``` |
163 | 94 |
|
164 | 95 | ## Parameters |
165 | 96 |
|
166 | 97 | | Parameter | Type | Required | Description | |
167 | 98 | |-----------|------|----------|-------------| |
168 | | -| `token` | string | Yes | The JWT token to parse (Bearer prefix will be automatically removed) | |
169 | | -| `operation` | string | No | Operation to perform: "parse", "extractUser", or "extractClaim" (default: "parse") | |
170 | | -| `claim` | string | No | Name of specific claim to extract (required when operation is "extractClaim") | |
| 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") | |
171 | 101 |
|
172 | 102 | ## Output |
173 | 103 |
|
174 | | -The function returns a JSON object containing: |
175 | | -- For `parse`: Complete JWT payload |
176 | | -- For `extractUser`: Standard user claims (sub, preferred_username, email, name, etc.) |
177 | | -- For `extractClaim`: The specific claim value |
| 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) |
178 | 107 |
|
179 | 108 | ## Token Format Support |
180 | 109 |
|
181 | 110 | The function supports JWT tokens in various formats: |
182 | 111 | - Raw JWT token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` |
183 | 112 | - Bearer token: `Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...` |
184 | 113 |
|
| 114 | +## jq Expression Details |
| 115 | + |
| 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 |
| 120 | + |
| 121 | +## Common Claim Paths |
| 122 | + |
| 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 |
| 132 | + |
185 | 133 | ## Error Handling |
186 | 134 |
|
187 | | -The function will fail if: |
| 135 | +The jq expressions will fail if: |
188 | 136 | - Token is null or empty |
189 | | -- Token format is invalid |
190 | | -- Required claim parameter is missing for extractClaim operation |
| 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 |
191 | 140 |
|
192 | 141 | ## Security Note |
193 | 142 |
|
|
0 commit comments