From ff49b6fc538fd704d59184ec3843060ac01ee0ea Mon Sep 17 00:00:00 2001 From: "deborah.digges" Date: Wed, 12 Aug 2026 15:41:27 +0000 Subject: [PATCH 1/2] Expose authentication method on action contexts The API now sends authentication_method on both action payloads; surface it on ActionContext so Actions can branch on how the user authenticated or signed up. Also export AuthenticationMethod, which gains the GrokOAuth and XOAuth values the API already returns. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/actions/actions.spec.ts | 2 ++ src/actions/fixtures/authentication-action-context.json | 1 + src/actions/fixtures/user-registration-action-context.json | 1 + src/actions/interfaces/action.interface.ts | 5 +++++ src/actions/serializers/action.serializer.ts | 2 ++ .../interfaces/authentication-response.interface.ts | 4 +++- 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/actions/actions.spec.ts b/src/actions/actions.spec.ts index 25af43269..7c5c1c531 100644 --- a/src/actions/actions.spec.ts +++ b/src/actions/actions.spec.ts @@ -131,6 +131,7 @@ describe('Actions', () => { externalId: null, metadata: {}, }, + authenticationMethod: 'Password', ipAddress: '50.141.123.10', userAgent: 'Mozilla/5.0', deviceFingerprint: 'notafingerprint', @@ -183,6 +184,7 @@ describe('Actions', () => { firstName: 'Jane', lastName: 'Doe', }, + authenticationMethod: 'GoogleOAuth', ipAddress: '50.141.123.10', userAgent: 'Mozilla/5.0', deviceFingerprint: 'notafingerprint', diff --git a/src/actions/fixtures/authentication-action-context.json b/src/actions/fixtures/authentication-action-context.json index 3dd48e7de..addea1928 100644 --- a/src/actions/fixtures/authentication-action-context.json +++ b/src/actions/fixtures/authentication-action-context.json @@ -12,6 +12,7 @@ "created_at": "2024-10-22T17:12:50.746Z", "updated_at": "2024-10-22T17:12:50.746Z" }, + "authentication_method": "Password", "ip_address": "50.141.123.10", "user_agent": "Mozilla/5.0", "device_fingerprint": "notafingerprint", diff --git a/src/actions/fixtures/user-registration-action-context.json b/src/actions/fixtures/user-registration-action-context.json index bfd422a17..3fd36a96a 100644 --- a/src/actions/fixtures/user-registration-action-context.json +++ b/src/actions/fixtures/user-registration-action-context.json @@ -7,6 +7,7 @@ "first_name": "Jane", "last_name": "Doe" }, + "authentication_method": "GoogleOAuth", "ip_address": "50.141.123.10", "user_agent": "Mozilla/5.0", "device_fingerprint": "notafingerprint", diff --git a/src/actions/interfaces/action.interface.ts b/src/actions/interfaces/action.interface.ts index bf1df04c0..c58da0c4f 100644 --- a/src/actions/interfaces/action.interface.ts +++ b/src/actions/interfaces/action.interface.ts @@ -3,6 +3,7 @@ import { OrganizationResponse, } from '../../organizations/interfaces'; import { + AuthenticationMethod, Invitation, InvitationResponse, OrganizationMembership, @@ -17,6 +18,7 @@ interface AuthenticationActionContext { user: User; organization?: Organization; organizationMembership?: OrganizationMembership; + authenticationMethod: AuthenticationMethod; ipAddress?: string; userAgent?: string; deviceFingerprint?: string; @@ -36,6 +38,7 @@ interface UserRegistrationActionContext { object: 'user_registration_action_context'; userData: UserData; invitation?: Invitation; + authenticationMethod: AuthenticationMethod; ipAddress?: string; userAgent?: string; deviceFingerprint?: string; @@ -50,6 +53,7 @@ interface AuthenticationActionPayload { user: UserResponse; organization?: OrganizationResponse; organization_membership?: OrganizationMembershipResponse; + authentication_method: AuthenticationMethod; ip_address?: string; user_agent?: string; device_fingerprint?: string; @@ -69,6 +73,7 @@ export interface UserRegistrationActionPayload { object: 'user_registration_action_context'; user_data: UserDataPayload; invitation?: InvitationResponse; + authentication_method: AuthenticationMethod; ip_address?: string; user_agent?: string; device_fingerprint?: string; diff --git a/src/actions/serializers/action.serializer.ts b/src/actions/serializers/action.serializer.ts index 1080249bc..fc0a2ddb1 100644 --- a/src/actions/serializers/action.serializer.ts +++ b/src/actions/serializers/action.serializer.ts @@ -34,6 +34,7 @@ export const deserializeAction = ( ? deserializeInvitation(actionPayload.invitation) : undefined, + authenticationMethod: actionPayload.authentication_method, ipAddress: actionPayload.ip_address, userAgent: actionPayload.user_agent, deviceFingerprint: actionPayload.device_fingerprint, @@ -51,6 +52,7 @@ export const deserializeAction = ( actionPayload.organization_membership, ) : undefined, + authenticationMethod: actionPayload.authentication_method, ipAddress: actionPayload.ip_address, userAgent: actionPayload.user_agent, deviceFingerprint: actionPayload.device_fingerprint, diff --git a/src/user-management/interfaces/authentication-response.interface.ts b/src/user-management/interfaces/authentication-response.interface.ts index 3fe4d329c..ebdd63687 100644 --- a/src/user-management/interfaces/authentication-response.interface.ts +++ b/src/user-management/interfaces/authentication-response.interface.ts @@ -2,7 +2,7 @@ import { Impersonator, ImpersonatorResponse } from './impersonator.interface'; import { OauthTokens, OauthTokensResponse } from './oauth-tokens.interface'; import { User, UserResponse } from './user.interface'; -type AuthenticationMethod = +export type AuthenticationMethod = | 'SSO' | 'Password' | 'Passkey' @@ -12,6 +12,8 @@ type AuthenticationMethod = | 'GitHubOAuth' | 'GitLabOAuth' | 'GoogleOAuth' + | 'GrokOAuth' + | 'XOAuth' | 'IntuitOAuth' | 'LinkedInOAuth' | 'MicrosoftOAuth' From d8eccc6deb3a175dbbe918ddbdde994aebcb28be Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Thu, 13 Aug 2026 09:20:16 -0400 Subject: [PATCH 2/2] fix: make authenticationMethod optional on action context types Declaring the field as required made it a type-level breaking change for anyone constructing an ActionPayload or ActionContext, and the deserializer cannot honor the guarantee anyway: constructAction casts parsed JSON without validating it, so a payload sent before the API rollout completes yields undefined behind a type that promises a value. Actions gate allow/deny verdicts, so a policy branching on the method should be forced to handle its absence rather than trust a nominal guarantee. Optional on all four types keeps the payload and context shapes consistent; splitting them would not compile in the serializer under strict mode. --- src/actions/actions.spec.ts | 14 ++++++++++++++ src/actions/interfaces/action.interface.ts | 8 ++++---- src/actions/serializers/action.serializer.ts | 1 - 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/actions/actions.spec.ts b/src/actions/actions.spec.ts index 7c5c1c531..128fcaae0 100644 --- a/src/actions/actions.spec.ts +++ b/src/actions/actions.spec.ts @@ -199,5 +199,19 @@ describe('Actions', () => { }), }); }); + + it('leaves the authentication method undefined when the payload omits it', async () => { + const { authentication_method: _omitted, ...payload } = + mockAuthActionContext; + const sigHeader = makeSigHeader(payload, secret); + const action = await workos.actions.constructAction({ + payload, + sigHeader, + secret, + }); + + expect(action.authenticationMethod).toBeUndefined(); + expect(action.object).toEqual('authentication_action_context'); + }); }); }); diff --git a/src/actions/interfaces/action.interface.ts b/src/actions/interfaces/action.interface.ts index c58da0c4f..c25edc74a 100644 --- a/src/actions/interfaces/action.interface.ts +++ b/src/actions/interfaces/action.interface.ts @@ -18,7 +18,7 @@ interface AuthenticationActionContext { user: User; organization?: Organization; organizationMembership?: OrganizationMembership; - authenticationMethod: AuthenticationMethod; + authenticationMethod?: AuthenticationMethod; ipAddress?: string; userAgent?: string; deviceFingerprint?: string; @@ -38,7 +38,7 @@ interface UserRegistrationActionContext { object: 'user_registration_action_context'; userData: UserData; invitation?: Invitation; - authenticationMethod: AuthenticationMethod; + authenticationMethod?: AuthenticationMethod; ipAddress?: string; userAgent?: string; deviceFingerprint?: string; @@ -53,7 +53,7 @@ interface AuthenticationActionPayload { user: UserResponse; organization?: OrganizationResponse; organization_membership?: OrganizationMembershipResponse; - authentication_method: AuthenticationMethod; + authentication_method?: AuthenticationMethod; ip_address?: string; user_agent?: string; device_fingerprint?: string; @@ -73,7 +73,7 @@ export interface UserRegistrationActionPayload { object: 'user_registration_action_context'; user_data: UserDataPayload; invitation?: InvitationResponse; - authentication_method: AuthenticationMethod; + authentication_method?: AuthenticationMethod; ip_address?: string; user_agent?: string; device_fingerprint?: string; diff --git a/src/actions/serializers/action.serializer.ts b/src/actions/serializers/action.serializer.ts index fc0a2ddb1..ea62ccdf8 100644 --- a/src/actions/serializers/action.serializer.ts +++ b/src/actions/serializers/action.serializer.ts @@ -33,7 +33,6 @@ export const deserializeAction = ( invitation: actionPayload.invitation ? deserializeInvitation(actionPayload.invitation) : undefined, - authenticationMethod: actionPayload.authentication_method, ipAddress: actionPayload.ip_address, userAgent: actionPayload.user_agent,