# Multi-Factor Authentication
## Introduction
The Multi-Factor Authentication (MFA) API is intended to be a composable, unopinionated set of endpoints that can be integrated into existing application/session management strategies.
The available types of authentication factors are:
- `totp` – Time-based one-time password
- `sms` – One-time password via SMS message
> The MFA API is not intended to be used with the WorkOS SSO feature. It’s recommended to leverage the MFA features of the Identity Provider that is powering your SSO implementation.
## What you’ll build
In this guide, we’ll walk you through the process of enrolling new authentication factors for a user, and the challenge/verification process for existing authentication factors.
This guide will show you how to:
1. Create an Authentication Factor
2. Challenge the Authentication Factor
3. Verify the Challenge
## Before getting started
To get the most out of this guide, you’ll need:
- A [WorkOS account](https://dashboard.workos.com/)
## API object definitions
[Authentication Factor](https://workos.com/docs/reference/mfa/factor)
: A factor of authentication that can be used in conjunction with a primary factor to provide multiple factors of authentication.
[Authentication Challenge](https://workos.com/docs/reference/mfa/challenge)
: A request for an Authentication Factor to be verified.
## (1) Create an Authentication Factor
We’ll first need to enroll a new Authentication Factor.
### Install the WorkOS SDK
WorkOS offers native SDKs in several popular programming languages. Choose a language below to see instructions in your application’s language.
### Set secrets
To make calls to WorkOS, provide the API key and, in some cases, the client ID. Store these values as managed secrets, such as `WORKOS_API_KEY` and `WORKOS_CLIENT_ID`, and pass them to the SDKs either as environment variables or directly in your app's configuration based on your preferences.
```plain title="Environment variables"
WORKOS_API_KEY='sk_example_123456789'
WORKOS_CLIENT_ID='client_123456789'
```
### Enroll the Authentication Factor
- | Using TOTP
Use the TOTP type when the user is using a third-party authenticator app such as Google Authenticator or Authy.
#### Enroll Endpoint
```js
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS('sk_example_123456789');
const factor = await workos.mfa.enrollFactor({
type: 'totp',
issuer: 'Foo Corp',
user: 'alan.turing@example.com',
});
```
```rb
require "workos"
WorkOS.configure do |config|
config.key = "sk_example_123456789"
end
factor = WorkOS::MFA.enroll_factor(
type: "totp",
totp_issuer: "Foo Corp",
totp_user: "alan.turing@example.com"
)
```
```py
from workos import WorkOSClient
workos_client = WorkOSClient(
api_key="sk_example_123456789", client_id="client_123456789"
)
factor_type = "totp"
organization = "Foo Corp"
email = "alan.turing@example.com"
response = workos_client.mfa.enroll_factor(
type=factor_type, totp_issuer=organization, totp_user=email
)
```
```go
package main
import (
"context"
"github.com/workos/workos-go/v3/pkg/mfa"
)
func main() {
mfa.SetAPIKey("sk_example_123456789")
enroll, err := mfa.EnrollFactor(context.Background(), mfa.EnrollFactorOpts{
Type: "totp",
TOTPIssuer: "Foo Corp",
TOTPUser: "alan.turing@example.com",
})
}
```
```php
enrollFactor(
type: $type,
totpIssuer: $totpIssuer,
totpUser: $totpUser
);
```
```php
enrollFactor(
type: $type,
totpIssuer: $totpIssuer,
totpUser: $totpUser
);
```
```java
import com.workos.WorkOS;
import com.workos.mfa.MfaApi.EnrollFactorOptions;
import com.workos.mfa.models.Factor;
WorkOS workos = new WorkOS("sk_example_123456789");
EnrollFactorOptions options = EnrollFactorOptions.builder()
.type("totp")
.issuer("Foo Corp")
.user("alan.turing@example.com")
.build();
Factor factor = workos.mfa.enrollFactor(options);
```
```cs
WorkOS.SetApiKey("sk_example_123456789");
var mfaService = new MfaService();
var options = new EnrollTotpFactorOptions("Foo Corp", "alan.turing@example.com");
var factor = await mfaService.EnrollFactor(options);
```
The response returns a `qr_code` and a secret. The `qr_code` value is a base64 encoded data URI that is used to [display the QR code](https://css-tricks.com/data-uris/) in your application for enrollment with an authenticator application.
The `secret` can be entered into some authenticator applications in place of scanning a QR code.
- | Using SMS
Use the SMS type when the user wants to receive one time passwords as SMS messages to their mobile device.
Phone number must be valid. An error will be returned for malformed or invalid phone numbers.
#### Enroll Endpoint
```js
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS('sk_example_123456789');
const factor = await workos.mfa.enrollFactor({
type: 'sms',
phoneNumber: '+15005550006',
});
```
```py
from workos import WorkOSClient
workos_client = WorkOSClient(
api_key="sk_example_123456789", client_id="client_123456789"
)
factor_type = "sms"
phone_number = "+15005550006"
response = workos_client.mfa.enroll_factor(type=factor_type, phone_number=phone_number)
```
```go
package main
import (
"context"
"github.com/workos/workos-go/v3/pkg/mfa"
)
func main() {
mfa.SetAPIKey("sk_example_123456789")
enroll, err := mfa.EnrollFactor(context.Background(), mfa.EnrollFactorOpts{
Type: "sms",
PhoneNumber: "+15005550006",
})
}
```
```php
enrollFactor(type: $type, phoneNumber: $phoneNumber);
```
```php
enrollFactor(type: $type, phoneNumber: $phoneNumber);
```
```java
import com.workos.WorkOS;
import com.workos.mfa.MfaApi.EnrollFactorOptions;
import com.workos.mfa.models.Factor;
WorkOS workos = new WorkOS("sk_example_123456789");
EnrollFactorOptions options =
EnrollFactorOptions.builder().type("sms").phoneNumber("+15005550006").build();
Factor factor = workos.mfa.enrollFactor(options);
```
```cs
WorkOS.SetApiKey("sk_example_123456789");
var mfaService = new MfaService();
var options = new EnrollSmsFactorOptions("+15005550006");
var factor = await mfaService.EnrollFactor(options);
```
Now that we’ve successfully created an authentication factor, we’ll need to save the ID for later use. It’s recommended that you persist the factor ID in your own user model according to your application’s needs.
## (2) Challenge the Authentication Factor
Next we’ll initiate the authentication process for the newly created factor which we’ll refer to as a challenge.
- | Create Authentication Challenge
#### Challenge Endpoint
```js
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS('sk_example_123456789');
const challenge = await workos.mfa.challengeFactor({
authenticationFactorId: 'auth_factor_01FVYZ5QM8N98T9ME5BCB2BBMJ',
});
```
```rb
require "workos"
WorkOS.configure do |config|
config.key = "sk_example_123456789"
end
challenge = WorkOS::MFA.challenge_factor(
authentication_factor_id: "auth_factor_01FZ4TS0MWPZR7GATS7KCXANQZ"
)
```
```py
from workos import WorkOSClient
workos_client = WorkOSClient(
api_key="sk_example_123456789", client_id="client_123456789"
)
factor_id = "auth_factor_01FY7SABJNSPYR7CT052GNDQ49"
response = workos_client.mfa.challenge_factor(authentication_factor_id=factor_id)
```
```go
package main
import (
"context"
"github.com/workos/workos-go/v3/pkg/mfa"
)
func main() {
mfa.SetAPIKey("sk_example_123456789")
challenge, err := mfa.ChallengeFactor(context.Background(), mfa.ChallengeFactorOpts{
FactorID: "auth_factor_01FVYZ5QM8N98T9ME5BCB2BBM",
})
}
```
```php
challengeFactor($authenticationFactorId);
```
```php
challengeFactor($authenticationFactorId);
```
```java
import com.workos.WorkOS;
import com.workos.mfa.MfaApi.ChallengeFactorOptions;
import com.workos.mfa.models.Challenge;
WorkOS workos = new WorkOS("sk_example_123456789");
String authenticationFactorId = "auth_factor_01FY7SABJNSPYR7CT052GNDQ49";
ChallengeFactorOptions options = ChallengeFactorOptions.builder()
.authenticationFactorId(authenticationFactorId)
.build();
Challenge challenge = workos.mfa.challengeFactor(options);
```
```cs
WorkOS.SetApiKey("sk_example_123456789");
var mfaService = new MfaService();
var options = new ChallengeFactorOptions {
FactorId = "auth_factor_01FVYZ5QM8N98T9ME5BCB2BBMJ",
};
var response = await mfaService.ChallengeFactor(options);
```
- | Sending Custom SMS Message
When challenging an SMS authentication factor, you can pass an optional SMS template to customize the SMS message that is sent to the end user. Use the `{{code}}` token to inject the one time password into the message.
#### Challenge Endpoint
```js
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS('sk_example_123456789');
const enrollResponse = await workos.mfa.challengeFactor({
authenticationFactorId: 'auth_factor_01FVYZ5QM8N98T9ME5BCB2BBMJ',
smsTemplate: 'Your FooCorp is {{code}}.',
});
```
```rb
require "workos"
WorkOS.configure do |config|
config.key = "sk_example_123456789"
end
challenge = WorkOS::MFA.challenge_factor(
authentication_factor_id: "auth_factor_01FZ4TS14D1PHFNZ9GF6YD8M1F",
sms_template: "Your code is {{code}}"
)
```
```py
from workos import WorkOSClient
workos_client = WorkOSClient(
api_key="sk_example_123456789", client_id="client_123456789"
)
factor_id = "auth_factor_01FY7SABJNSPYR7CT052GNDQ49"
message = "Your code is {{code}}"
response = workos_client.mfa.challenge_factor(
authentication_factor_id=factor_id, sms_template=message
)
```
```go
package main
import (
"context"
"github.com/workos/workos-go/v3/pkg/mfa"
)
func main() {
mfa.SetAPIKey("sk_example_123456789")
challenge, err := mfa.ChallengeFactor(context.Background(), mfa.ChallengeFactorOpts{
FactorID: "auth_factor_01FVYZ5QM8N98T9ME5BCB2BBM",
SMSTemplate: "Your code is {{code}}",
})
}
```
```php
challengeFactor($authenticationFactorId, $smsTemplate);
```
```php
challengeFactor($authenticationFactorId, $smsTemplate);
```
```java
import com.workos.WorkOS;
import com.workos.mfa.MfaApi.ChallengeFactorOptions;
import com.workos.mfa.models.Challenge;
WorkOS workos = new WorkOS("sk_example_123456789");
String authenticationFactorId = "auth_factor_01FY7SABJNSPYR7CT052GNDQ49";
String smsTemplate = "Your code is {{code}}";
ChallengeFactorOptions options = ChallengeFactorOptions.builder()
.authenticationFactorId(authenticationFactorId)
.smsTemplate(smsTemplate)
.build();
Challenge challenge = workos.mfa.challengeFactor(options);
```
```cs
WorkOS.SetApiKey("sk_example_123456789");
var mfaService = new MfaService();
var options = new ChallengeSmsFactorOptions("Your FooCorp is {{code}}.") {
FactorId = "auth_factor_01FVYZ5QM8N98T9ME5BCB2BBMJ",
};
var challenge = await mfaService.ChallengeFactor(options);
```
Now that we’ve successfully challenged the authentication factor, we’ll need to save the challenge ID for the last step, challenge verification.
## (3) Verify the Challenge
The last step in the authentication process is to verify the one time password provided by the end-user.
#### Verify Endpoint
```js
import { WorkOS } from '@workos-inc/node';
const workos = new WorkOS('sk_example_123456789');
const { challenge, valid } = await workos.mfa.verifyChallenge({
authenticationChallengeId: 'auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5',
code: '123456',
});
```
```rb
require "workos"
WorkOS.configure do |config|
config.key = "sk_example_123456789"
end
response = WorkOS::MFA.verify_challenge(
authentication_challenge_id: "auth_challenge_01FZ4YVRBMXP5ZM0A7BP4AJ12J",
code: "123456"
)
```
```py
from workos import WorkOSClient
workos_client = WorkOSClient(
api_key="sk_example_123456789", client_id="client_123456789"
)
challenge_id = "auth_challenge_01FY7SGMZVEQ8AAGZER2XRQ5NJ"
challenge_code = "123456"
response = workos_client.mfa.verify_challenge(
authentication_challenge_id=challenge_id, code=challenge_code
)
```
```go
package main
import (
"context"
"github.com/workos/workos-go/v3/pkg/mfa"
)
func main() {
mfa.SetAPIKey("sk_example_123456789")
verify, err := mfa.VerifyChallenge(context.Background(), mfa.VerifyChallengeOpts{
ChallengeID: "auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5",
Code: "123456",
})
}
```
```php
verifyChallenge($authenticationChallengeId, $code);
```
```php
verifyChallenge($authenticationChallengeId, $code);
```
```java
import com.workos.WorkOS;
import com.workos.mfa.MfaApi.VerifyChallengeOptions;
import com.workos.mfa.MfaApi.VerifyChallengeResponse;
WorkOS workos = new WorkOS("sk_example_123456789");
String authenticationChallengeId = "auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5";
String code = "123456";
VerifyChallengeOptions options = VerifyChallengeOptions.builder()
.authenticationChallengeId(authenticationChallengeId)
.code(code)
.build();
VerifyChallengeResponse response = workos.mfa.verifyChallenge(options);
```
```cs
WorkOS.SetApiKey("sk_example_123456789");
var mfaService = new MfaService();
var options = new VerifyChallengeOptions {
ChallengeId = "auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5",
Code = "12345",
};
var response = await mfaService.VerifyChallenge(options);
```
### Verification Response
If the challenge is successfully verified `valid` will return `true`. Otherwise it will return `false` and another verification attempt must be made.
#### Response
```json
{
"challenge": {
"object": "authentication_challenge",
"id": "auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5",
"created_at": "2022-02-15T15:26:53.274Z",
"updated_at": "2022-02-15T15:26:53.274Z",
"expires_at": "2022-02-15T15:36:53.279Z",
"authentication_factor_id": "auth_factor_01FVYZ5QM8N98T9ME5BCB2BBMJ"
},
"valid": true
}
```
### Already Verified Error
If a challenge was already successfully verified, it cannot be used a second time. If further verification is needed in your application, create a new challenge.
#### Response
```json
{
"code": "authentication_challenge_previously_verified",
"message": "The authentication challenge 'auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5' has already been verified."
}
```
### Expired Error
For SMS authentication factors, challenges are only available for verification for 10 minutes. After that they are expired and cannot be verified.
#### Response
```json
{
"code": "authentication_challenge_expired",
"message": "The authentication challenge 'auth_challenge_01FVYZWQTZQ5VB6BC5MPG2EYC5' has expired."
}
```
We’ve now successfully verified an end-user’s authentication factor. This authentication factor can now be used as a second factor of authentication in your application’s existing authentication strategy.
The ID of the authentication factor should be persisted in your application for future authentication challenges.