stytch 6.6.0 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -84,6 +84,7 @@ module StytchB2B
84
84
  mfa_phone_number: nil,
85
85
  locale: nil
86
86
  )
87
+ headers = {}
87
88
  request = {
88
89
  organization_id: organization_id,
89
90
  member_id: member_id
@@ -91,7 +92,7 @@ module StytchB2B
91
92
  request[:mfa_phone_number] = mfa_phone_number unless mfa_phone_number.nil?
92
93
  request[:locale] = locale unless locale.nil?
93
94
 
94
- post_request('/v1/b2b/otps/sms/send', request)
95
+ post_request('/v1/b2b/otps/sms/send', request, headers)
95
96
  end
96
97
 
97
98
  # SMS OTPs may not be used as a primary authentication mechanism. They can be used to complete an MFA requirement, or they can be used as a step-up factor to be added to an existing session.
@@ -197,6 +198,7 @@ module StytchB2B
197
198
  session_custom_claims: nil,
198
199
  set_mfa_enrollment: nil
199
200
  )
201
+ headers = {}
200
202
  request = {
201
203
  organization_id: organization_id,
202
204
  member_id: member_id,
@@ -209,7 +211,7 @@ module StytchB2B
209
211
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
210
212
  request[:set_mfa_enrollment] = set_mfa_enrollment unless set_mfa_enrollment.nil?
211
213
 
212
- post_request('/v1/b2b/otps/sms/authenticate', request)
214
+ post_request('/v1/b2b/otps/sms/authenticate', request, headers)
213
215
  end
214
216
  end
215
217
  end
@@ -78,12 +78,13 @@ module StytchB2B
78
78
  password:,
79
79
  email_address: nil
80
80
  )
81
+ headers = {}
81
82
  request = {
82
83
  password: password
83
84
  }
84
85
  request[:email_address] = email_address unless email_address.nil?
85
86
 
86
- post_request('/v1/b2b/passwords/strength_check', request)
87
+ post_request('/v1/b2b/passwords/strength_check', request, headers)
87
88
  end
88
89
 
89
90
  # Adds an existing password to a member's email that doesn't have a password yet. We support migrating members from passwords stored with bcrypt, scrypt, argon2, MD-5, SHA-1, and PBKDF2. This endpoint has a rate limit of 100 requests per second.
@@ -127,6 +128,21 @@ module StytchB2B
127
128
  # frontend SDK, and should not be used to store critical information. See the [Metadata resource](https://stytch.com/docs/b2b/api/metadata)
128
129
  # for complete field behavior details.
129
130
  # The type of this field is nilable +object+.
131
+ # roles::
132
+ # (Coming Soon) Roles to explicitly assign to this Member.
133
+ # Will completely replace any existing explicitly assigned roles. See the
134
+ # [RBAC guide](https://stytch.com/docs/b2b/guides/rbac/role-assignment) for more information about role assignment.
135
+ #
136
+ # If a Role is removed from a Member, and the Member is also implicitly assigned this Role from an SSO connection
137
+ # or an SSO group, we will by default revoke any existing sessions for the Member that contain any SSO
138
+ # authentication factors with the affected connection ID. You can preserve these sessions by passing in the
139
+ # `preserve_existing_sessions` parameter with a value of `true`.
140
+ # The type of this field is nilable list of +String+.
141
+ # preserve_existing_sessions::
142
+ # (Coming Soon) Whether to preserve existing sessions when explicit Roles that are revoked are also implicitly assigned
143
+ # by SSO connection or SSO group. Defaults to `false` - that is, existing Member Sessions that contain SSO
144
+ # authentication factors with the affected SSO connection IDs will be revoked.
145
+ # The type of this field is nilable +Boolean+.
130
146
  #
131
147
  # == Returns:
132
148
  # An object with the following fields:
@@ -160,8 +176,11 @@ module StytchB2B
160
176
  pbkdf_2_config: nil,
161
177
  name: nil,
162
178
  trusted_metadata: nil,
163
- untrusted_metadata: nil
179
+ untrusted_metadata: nil,
180
+ roles: nil,
181
+ preserve_existing_sessions: nil
164
182
  )
183
+ headers = {}
165
184
  request = {
166
185
  email_address: email_address,
167
186
  hash: hash,
@@ -176,8 +195,10 @@ module StytchB2B
176
195
  request[:name] = name unless name.nil?
177
196
  request[:trusted_metadata] = trusted_metadata unless trusted_metadata.nil?
178
197
  request[:untrusted_metadata] = untrusted_metadata unless untrusted_metadata.nil?
198
+ request[:roles] = roles unless roles.nil?
199
+ request[:preserve_existing_sessions] = preserve_existing_sessions unless preserve_existing_sessions.nil?
179
200
 
180
- post_request('/v1/b2b/passwords/migrate', request)
201
+ post_request('/v1/b2b/passwords/migrate', request, headers)
181
202
  end
182
203
 
183
204
  # Authenticate a member with their email address and password. This endpoint verifies that the member has a password currently set, and that the entered password is correct.
@@ -285,6 +306,7 @@ module StytchB2B
285
306
  session_custom_claims: nil,
286
307
  locale: nil
287
308
  )
309
+ headers = {}
288
310
  request = {
289
311
  organization_id: organization_id,
290
312
  email_address: email_address,
@@ -296,7 +318,7 @@ module StytchB2B
296
318
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
297
319
  request[:locale] = locale unless locale.nil?
298
320
 
299
- post_request('/v1/b2b/passwords/authenticate', request)
321
+ post_request('/v1/b2b/passwords/authenticate', request, headers)
300
322
  end
301
323
 
302
324
  class Email
@@ -374,6 +396,7 @@ module StytchB2B
374
396
  locale: nil,
375
397
  reset_password_template_id: nil
376
398
  )
399
+ headers = {}
377
400
  request = {
378
401
  organization_id: organization_id,
379
402
  email_address: email_address
@@ -385,7 +408,7 @@ module StytchB2B
385
408
  request[:locale] = locale unless locale.nil?
386
409
  request[:reset_password_template_id] = reset_password_template_id unless reset_password_template_id.nil?
387
410
 
388
- post_request('/v1/b2b/passwords/email/reset/start', request)
411
+ post_request('/v1/b2b/passwords/email/reset/start', request, headers)
389
412
  end
390
413
 
391
414
  # Reset the member's password and authenticate them. This endpoint checks that the password reset token is valid, hasn’t expired, or already been used.
@@ -500,6 +523,7 @@ module StytchB2B
500
523
  session_custom_claims: nil,
501
524
  locale: nil
502
525
  )
526
+ headers = {}
503
527
  request = {
504
528
  password_reset_token: password_reset_token,
505
529
  password: password
@@ -511,7 +535,7 @@ module StytchB2B
511
535
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
512
536
  request[:locale] = locale unless locale.nil?
513
537
 
514
- post_request('/v1/b2b/passwords/email/reset', request)
538
+ post_request('/v1/b2b/passwords/email/reset', request, headers)
515
539
  end
516
540
  end
517
541
 
@@ -611,6 +635,7 @@ module StytchB2B
611
635
  session_custom_claims: nil,
612
636
  locale: nil
613
637
  )
638
+ headers = {}
614
639
  request = {
615
640
  organization_id: organization_id,
616
641
  password: password
@@ -621,7 +646,7 @@ module StytchB2B
621
646
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
622
647
  request[:locale] = locale unless locale.nil?
623
648
 
624
- post_request('/v1/b2b/passwords/session/reset', request)
649
+ post_request('/v1/b2b/passwords/session/reset', request, headers)
625
650
  end
626
651
  end
627
652
 
@@ -742,6 +767,7 @@ module StytchB2B
742
767
  session_custom_claims: nil,
743
768
  locale: nil
744
769
  )
770
+ headers = {}
745
771
  request = {
746
772
  email_address: email_address,
747
773
  existing_password: existing_password,
@@ -754,7 +780,7 @@ module StytchB2B
754
780
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
755
781
  request[:locale] = locale unless locale.nil?
756
782
 
757
- post_request('/v1/b2b/passwords/existing_password/reset', request)
783
+ post_request('/v1/b2b/passwords/existing_password/reset', request, headers)
758
784
  end
759
785
  end
760
786
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # !!!
4
+ # WARNING: This file is autogenerated
5
+ # Only modify code within MANUAL() sections
6
+ # or your changes may be overwritten later!
7
+ # !!!
8
+
9
+ require_relative 'request_helper'
10
+
11
+ module StytchB2B
12
+ class RBAC
13
+ include Stytch::RequestHelper
14
+
15
+ def initialize(connection)
16
+ @connection = connection
17
+ end
18
+
19
+ # Get the active RBAC Policy for your current Stytch Project. An RBAC Policy is the canonical document that stores all defined Resources and Roles within your RBAC permissioning model.
20
+ #
21
+ # When using the backend SDKs, the RBAC Policy will automatically be loaded and refreshed in the background to allow for local evaluations, eliminating the need for an extra request to Stytch.
22
+ #
23
+ # Resources and Roles can be created and managed within the [Dashboard](/dashboard). Additionally, [Role assignment](https://stytch.com/docs/b2b/guides/rbac/role-assignment) can be programmatically managed through certain Stytch API endpoints.
24
+ #
25
+ #
26
+ # Check out the [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview) to learn more about Stytch's RBAC permissioning model or [contact us](https://share.hsforms.com/1qkU__-1CT1--lnqRDxphXgd4bkb) to request early access.
27
+ #
28
+ # == Parameters:
29
+ #
30
+ # == Returns:
31
+ # An object with the following fields:
32
+ # request_id::
33
+ # Globally unique UUID that is returned with every API call. This value is important to log for debugging purposes; we may ask for this value to help identify a specific API call when helping you debug an issue.
34
+ # The type of this field is +String+.
35
+ # status_code::
36
+ # The HTTP status code of the response. Stytch follows standard HTTP response status code patterns, e.g. 2XX values equate to success, 3XX values are redirects, 4XX are client errors, and 5XX are server errors.
37
+ # The type of this field is +Integer+.
38
+ # policy::
39
+ # The RBAC Policy document that contains all defined Roles and Resources – which are managed in the [Dashboard](/dashboard). Read more about these entities and how they work in our [RBAC overview](https://stytch.com/docs/b2b/guides/rbac/overview).
40
+ # The type of this field is nilable +Policy+ (+object+).
41
+ def policy
42
+ headers = {}
43
+ query_params = {}
44
+ request = request_with_query_params('/v1/b2b/rbac/policy', query_params)
45
+ get_request(request, headers)
46
+ end
47
+ end
48
+ end
@@ -15,9 +15,10 @@ module StytchB2B
15
15
  class Sessions
16
16
  include Stytch::RequestHelper
17
17
 
18
- def initialize(connection, project_id)
18
+ def initialize(connection, project_id, policy_cache)
19
19
  @connection = connection
20
20
 
21
+ @policy_cache = policy_cache
21
22
  @project_id = project_id
22
23
  @cache_last_update = 0
23
24
  @jwks_loader = lambda do |options|
@@ -58,18 +59,26 @@ module StytchB2B
58
59
  organization_id:,
59
60
  member_id:
60
61
  )
62
+ headers = {}
61
63
  query_params = {
62
64
  organization_id: organization_id,
63
65
  member_id: member_id
64
66
  }
65
67
  request = request_with_query_params('/v1/b2b/sessions', query_params)
66
- get_request(request)
68
+ get_request(request, headers)
67
69
  end
68
70
 
69
71
  # Authenticates a Session and updates its lifetime by the specified `session_duration_minutes`. If the `session_duration_minutes` is not specified, a Session will not be extended. This endpoint requires either a `session_jwt` or `session_token` be included in the request. It will return an error if both are present.
70
72
  #
71
73
  # You may provide a JWT that needs to be refreshed and is expired according to its `exp` claim. A new JWT will be returned if both the signature and the underlying Session are still valid.
72
74
  #
75
+ # If an `authorization_check` object is passed in, this method will also check if the Member is authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if their Member Session contains a Role, assigned [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
76
+ # In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
77
+ #
78
+ # If the Member is not authorized to perform the specified action on the specified Resource, or if the
79
+ # `organization_id` does not match the Member's Organization, a 403 error will be thrown.
80
+ # Otherwise, the response will contain a list of Roles that satisfied the authorization check.
81
+ #
73
82
  # == Parameters:
74
83
  # session_token::
75
84
  # A secret token for a given Stytch Session.
@@ -95,6 +104,21 @@ module StytchB2B
95
104
  # delete a key, supply a null value. Custom claims made with reserved claims (`iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti`) will be ignored.
96
105
  # Total custom claims size cannot exceed four kilobytes.
97
106
  # The type of this field is nilable +object+.
107
+ # authorization_check::
108
+ # (Coming Soon) If an `authorization_check` object is passed in, this endpoint will also check if the Member is
109
+ # authorized to perform the given action on the given Resource in the specified Organization. A Member is authorized if
110
+ # their Member Session contains a Role, assigned
111
+ # [explicitly or implicitly](https://github.com/docs/b2b/guides/rbac/role-assignment), with adequate permissions.
112
+ # In addition, the `organization_id` passed in the authorization check must match the Member's Organization.
113
+ #
114
+ # The Roles on the Member Session may differ from the Roles you see on the Member object - Roles that are implicitly
115
+ # assigned by SSO connection or SSO group will only be valid for a Member Session if there is at least one authentication
116
+ # factor on the Member Session from the specified SSO connection.
117
+ #
118
+ # If the Member is not authorized to perform the specified action on the specified Resource, or if the
119
+ # `organization_id` does not match the Member's Organization, a 403 error will be thrown.
120
+ # Otherwise, the response will contain a list of Roles that satisfied the authorization check.
121
+ # The type of this field is nilable +AuthorizationCheck+ (+object+).
98
122
  #
99
123
  # == Returns:
100
124
  # An object with the following fields:
@@ -119,19 +143,26 @@ module StytchB2B
119
143
  # status_code::
120
144
  # The HTTP status code of the response. Stytch follows standard HTTP response status code patterns, e.g. 2XX values equate to success, 3XX values are redirects, 4XX are client errors, and 5XX are server errors.
121
145
  # The type of this field is +Integer+.
146
+ # verdict::
147
+ # (Coming Soon) If an `authorization_check` is provided in the request and the check succeeds, this field will return
148
+ # the complete list of Roles that gave the Member permission to perform the specified action on the specified Resource.
149
+ # The type of this field is nilable +AuthorizationVerdict+ (+object+).
122
150
  def authenticate(
123
151
  session_token: nil,
124
152
  session_duration_minutes: nil,
125
153
  session_jwt: nil,
126
- session_custom_claims: nil
154
+ session_custom_claims: nil,
155
+ authorization_check: nil
127
156
  )
157
+ headers = {}
128
158
  request = {}
129
159
  request[:session_token] = session_token unless session_token.nil?
130
160
  request[:session_duration_minutes] = session_duration_minutes unless session_duration_minutes.nil?
131
161
  request[:session_jwt] = session_jwt unless session_jwt.nil?
132
162
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
163
+ request[:authorization_check] = authorization_check unless authorization_check.nil?
133
164
 
134
- post_request('/v1/b2b/sessions/authenticate', request)
165
+ post_request('/v1/b2b/sessions/authenticate', request, headers)
135
166
  end
136
167
 
137
168
  # Revoke a Session and immediately invalidate all its tokens. To revoke a specific Session, pass either the `member_session_id`, `session_token`, or `session_jwt`. To revoke all Sessions for a Member, pass the `member_id`.
@@ -164,13 +195,14 @@ module StytchB2B
164
195
  session_jwt: nil,
165
196
  member_id: nil
166
197
  )
198
+ headers = {}
167
199
  request = {}
168
200
  request[:member_session_id] = member_session_id unless member_session_id.nil?
169
201
  request[:session_token] = session_token unless session_token.nil?
170
202
  request[:session_jwt] = session_jwt unless session_jwt.nil?
171
203
  request[:member_id] = member_id unless member_id.nil?
172
204
 
173
- post_request('/v1/b2b/sessions/revoke', request)
205
+ post_request('/v1/b2b/sessions/revoke', request, headers)
174
206
  end
175
207
 
176
208
  # Use this endpoint to exchange a Member's existing session for another session in a different Organization. This can be used to accept an invite, but not to create a new member via domain matching.
@@ -270,6 +302,7 @@ module StytchB2B
270
302
  session_custom_claims: nil,
271
303
  locale: nil
272
304
  )
305
+ headers = {}
273
306
  request = {
274
307
  organization_id: organization_id
275
308
  }
@@ -279,7 +312,7 @@ module StytchB2B
279
312
  request[:session_custom_claims] = session_custom_claims unless session_custom_claims.nil?
280
313
  request[:locale] = locale unless locale.nil?
281
314
 
282
- post_request('/v1/b2b/sessions/exchange', request)
315
+ post_request('/v1/b2b/sessions/exchange', request, headers)
283
316
  end
284
317
 
285
318
  # Get the JSON Web Key Set (JWKS) for a project.
@@ -311,9 +344,10 @@ module StytchB2B
311
344
  def get_jwks(
312
345
  project_id:
313
346
  )
347
+ headers = {}
314
348
  query_params = {}
315
349
  request = request_with_query_params("/v1/b2b/sessions/jwks/#{project_id}", query_params)
316
- get_request(request)
350
+ get_request(request, headers)
317
351
  end
318
352
 
319
353
  # MANUAL(Sessions::authenticate_jwt)(SERVICE_METHOD)
@@ -325,38 +359,43 @@ module StytchB2B
325
359
  # If max_token_age_seconds is set and the JWT was issued (based on the "iat" claim) less than
326
360
  # max_token_age_seconds seconds ago, then just verify locally and don't call the API
327
361
  # To force remote validation for all tokens, set max_token_age_seconds to 0 or call authenticate()
362
+ # Note that the 'user_id' field of the returned session is DEPRECATED: Use member_id instead
363
+ # This field will be removed in a future MAJOR release.
364
+ # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
328
365
  def authenticate_jwt(
329
366
  session_jwt,
330
367
  max_token_age_seconds: nil,
331
368
  session_duration_minutes: nil,
332
- session_custom_claims: nil
369
+ session_custom_claims: nil,
370
+ authorization_check: nil
333
371
  )
372
+ max_token_age_seconds = 300 if max_token_age_seconds.nil?
373
+
334
374
  if max_token_age_seconds == 0
335
375
  return authenticate(
336
376
  session_jwt: session_jwt,
337
377
  session_duration_minutes: session_duration_minutes,
338
- session_custom_claims: session_custom_claims
378
+ session_custom_claims: session_custom_claims,
379
+ authorization_check: authorization_check
339
380
  )
340
381
  end
341
382
 
342
- decoded_jwt = authenticate_jwt_local(session_jwt)
343
- iat_time = Time.at(decoded_jwt['iat']).to_datetime
344
- if iat_time + max_token_age_seconds >= Time.now
345
- session = marshal_jwt_into_session(decoded_jwt)
346
- { 'session' => session }
347
- else
348
- authenticate(
349
- session_jwt: session_jwt,
350
- session_duration_minutes: session_duration_minutes,
351
- session_custom_claims: session_custom_claims
352
- )
353
- end
383
+ decoded_jwt = authenticate_jwt_local(session_jwt: session_jwt, authorization_check: authorization_check)
384
+ return decoded_jwt unless decoded_jwt.nil?
385
+
386
+ authenticate(
387
+ session_jwt: session_jwt,
388
+ session_duration_minutes: session_duration_minutes,
389
+ session_custom_claims: session_custom_claims,
390
+ authorization_check: authorization_check
391
+ )
354
392
  rescue StandardError
355
393
  # JWT could not be verified locally. Check with the Stytch API.
356
394
  authenticate(
357
395
  session_jwt: session_jwt,
358
396
  session_duration_minutes: session_duration_minutes,
359
- session_custom_claims: session_custom_claims
397
+ session_custom_claims: session_custom_claims,
398
+ authorization_check: authorization_check
360
399
  )
361
400
  end
362
401
 
@@ -364,41 +403,70 @@ module StytchB2B
364
403
  # Uses the cached value to get the JWK but if it is unavailable, it calls the get_jwks()
365
404
  # function to get the JWK
366
405
  # This method never authenticates a JWT directly with the API
367
- def authenticate_jwt_local(session_jwt)
406
+ # If max_token_age_seconds is not supplied 300 seconds will be used as the default.
407
+ def authenticate_jwt_local(session_jwt, max_token_age_seconds: nil, authorization_check: nil)
408
+ max_token_age_seconds = 300 if max_token_age_seconds.nil?
409
+
368
410
  issuer = 'stytch.com/' + @project_id
369
411
  begin
370
412
  decoded_token = JWT.decode session_jwt, nil, true,
371
413
  { jwks: @jwks_loader, iss: issuer, verify_iss: true, aud: @project_id, verify_aud: true, algorithms: ['RS256'] }
372
- decoded_token[0]
414
+
415
+ session = decoded_token[0]
416
+ iat_time = Time.at(session['iat']).to_datetime
417
+ return nil unless iat_time + max_token_age_seconds >= Time.now
418
+
419
+ session = marshal_jwt_into_session(session)
373
420
  rescue JWT::InvalidIssuerError
374
- raise JWTInvalidIssuerError
421
+ raise Stytch::JWTInvalidIssuerError
375
422
  rescue JWT::InvalidAudError
376
- raise JWTInvalidAudienceError
423
+ raise Stytch::JWTInvalidAudienceError
377
424
  rescue JWT::ExpiredSignature
378
- raise JWTExpiredSignatureError
425
+ raise Stytch::JWTExpiredSignatureError
379
426
  rescue JWT::IncorrectAlgorithm
380
- raise JWTIncorrectAlgorithmError
427
+ raise Stytch::JWTIncorrectAlgorithmError
428
+ end
429
+
430
+ # Do the auth check - intentionally don't rescue errors from here
431
+ if authorization_check && session['roles']
432
+ @policy_cache.perform_authorization_check(
433
+ subject_roles: session['roles'],
434
+ subject_org_id: session['member_session']['organization_id'],
435
+ authorization_check: authorization_check
436
+ )
381
437
  end
438
+
439
+ session
382
440
  end
383
441
 
442
+ # Note that the 'user_id' field is DEPRECATED: Use member_id instead
443
+ # This field will be removed in a future MAJOR release.
384
444
  def marshal_jwt_into_session(jwt)
385
445
  stytch_claim = 'https://stytch.com/session'
446
+ organization_claim = 'https://stytch.com/organization'
447
+
386
448
  expires_at = jwt[stytch_claim]['expires_at'] || Time.at(jwt['exp']).to_datetime.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
387
449
  # The custom claim set is all the claims in the payload except for the standard claims and
388
450
  # the Stytch session claim. The cleanest way to collect those seems to be naming what we want
389
451
  # to omit and filtering the rest to collect the custom claims.
390
- reserved_claims = ['aud', 'exp', 'iat', 'iss', 'jti', 'nbf', 'sub', stytch_claim]
452
+ reserved_claims = ['aud', 'exp', 'iat', 'iss', 'jti', 'nbf', 'sub', stytch_claim, organization_claim]
391
453
  custom_claims = jwt.reject { |key, _| reserved_claims.include?(key) }
392
454
  {
393
- 'session_id' => jwt[stytch_claim]['id'],
394
- 'user_id' => jwt['sub'],
395
- 'started_at' => jwt[stytch_claim]['started_at'],
396
- 'last_accessed_at' => jwt[stytch_claim]['last_accessed_at'],
397
- # For JWTs that include it, prefer the inner expires_at claim.
398
- 'expires_at' => expires_at,
399
- 'attributes' => jwt[stytch_claim]['attributes'],
400
- 'authentication_factors' => jwt[stytch_claim]['authentication_factors'],
401
- 'custom_claims' => custom_claims
455
+ 'member_session' => {
456
+ 'session_id' => jwt[stytch_claim]['id'],
457
+ 'organization_id' => jwt[organization_claim]['organization_id'],
458
+ 'member_id' => jwt['sub'],
459
+ # DEPRECATED: Use member_id instead
460
+ 'user_id' => jwt['sub'],
461
+ 'started_at' => jwt[stytch_claim]['started_at'],
462
+ 'last_accessed_at' => jwt[stytch_claim]['last_accessed_at'],
463
+ # For JWTs that include it, prefer the inner expires_at claim.
464
+ 'expires_at' => expires_at,
465
+ 'attributes' => jwt[stytch_claim]['attributes'],
466
+ 'authentication_factors' => jwt[stytch_claim]['authentication_factors'],
467
+ 'custom_claims' => custom_claims
468
+ },
469
+ 'roles' => jwt[stytch_claim]['roles']
402
470
  }
403
471
  end
404
472
  # ENDMANUAL(Sessions::authenticate_jwt)