diff --git a/.changeset/sign-in-token-second-factor.md b/.changeset/sign-in-token-second-factor.md new file mode 100644 index 00000000000..48d643fce47 --- /dev/null +++ b/.changeset/sign-in-token-second-factor.md @@ -0,0 +1,5 @@ +--- +'@clerk/backend': minor +--- + +Add an optional `secondFactorVerification` parameter (`'fresh' | 'stale'`) to `createSignInToken()`, which lets a user with a second factor enabled complete the sign-in without being prompted for it. `expiresInSeconds` is now optional. diff --git a/packages/backend/src/api/__tests__/SignInTokenApi.test.ts b/packages/backend/src/api/__tests__/SignInTokenApi.test.ts index 6d144fecbc9..91ab1c55382 100644 --- a/packages/backend/src/api/__tests__/SignInTokenApi.test.ts +++ b/packages/backend/src/api/__tests__/SignInTokenApi.test.ts @@ -43,4 +43,36 @@ describe('SignInTokenAPI', () => { expect(response.id).toBe('sign_in_token_123'); }); + + it('sends the second factor verification as second_factor_verification', async () => { + server.use( + http.post( + 'https://api.clerk.test/v1/sign_in_tokens', + validateHeaders(async ({ request }) => { + const body = await request.json(); + expect(body).toEqual({ + user_id: 'user_123', + second_factor_verification: 'stale', + }); + + return HttpResponse.json({ + id: 'sign_in_token_123', + user_id: 'user_123', + token: 'token_123', + status: 'pending', + url: 'https://accounts.example.com/tickets/token_123', + created_at: 1, + updated_at: 1, + }); + }), + ), + ); + + const response = await apiClient.signInTokens.createSignInToken({ + userId: 'user_123', + secondFactorVerification: 'stale', + }); + + expect(response.id).toBe('sign_in_token_123'); + }); }); diff --git a/packages/backend/src/api/endpoints/SignInTokenApi.ts b/packages/backend/src/api/endpoints/SignInTokenApi.ts index 45cb23efb32..006ea0c5c49 100644 --- a/packages/backend/src/api/endpoints/SignInTokenApi.ts +++ b/packages/backend/src/api/endpoints/SignInTokenApi.ts @@ -8,8 +8,10 @@ export type CreateSignInTokensParams = { userId: string; /** The ID of the organization to activate when the user signs in. Organizations must be enabled for the instance, and the user must be a member of the organization. */ orgId?: string; - /** The number of seconds until the sign-in token expires. By default, the duration is `2592000` (30 days). */ - expiresInSeconds: number; + /** The number of seconds until the sign-in token expires. By default, the duration is `2592000` (30 days), or `600` (10 minutes) when `secondFactorVerification` is set. */ + expiresInSeconds?: number; + /** Lets a user with a second factor enabled complete the sign-in without being prompted for it. With `fresh`, the second factor is treated as verified at sign-in. With `stale`, it is treated as unverified, so any reverification that requires a second factor prompts the user to complete one. Has no effect for users without a second factor enabled. */ + secondFactorVerification?: 'fresh' | 'stale'; }; const basePath = '/sign_in_tokens'; @@ -17,7 +19,7 @@ const basePath = '/sign_in_tokens'; /** @generateWithEmptyComment */ export class SignInTokenAPI extends AbstractAPI { /** - * Creates a new sign-in token for the given user. By default, sign-in tokens expire in 30 days. You can optionally specify a custom expiration time in seconds using the `expiresInSeconds` parameter. + * Creates a new sign-in token for the given user. By default, sign-in tokens expire in 30 days, or in 10 minutes when `secondFactorVerification` is set. You can optionally specify a custom expiration time in seconds using the `expiresInSeconds` parameter. * @returns The created [`SignInToken`](https://clerk.com/docs/reference/backend/types/backend-sign-in-token) object. */ public async createSignInToken(params: CreateSignInTokensParams) {