Skip to content

Instantly share code, notes, and snippets.

@perkinsjr
Created June 7, 2023 20:11
Show Gist options
  • Save perkinsjr/688a1b9a22c533173479c46579f0483c to your computer and use it in GitHub Desktop.
Save perkinsjr/688a1b9a22c533173479c46579f0483c to your computer and use it in GitHub Desktop.
Shows multiple factor in custom flow
import { useState } from "react";
import { useSignIn } from "@clerk/nextjs";
import { useRouter } from "next/router";
export default function SignInForm() {
const { isLoaded, signIn, setActive } = useSignIn();
const [emailAddress, setEmailAddress] = useState("");
const [password, setPassword] = useState("");
const [totp, setTotp] = useState("");
const [needsVerify, setNeedsVerify] = useState(false);
const [needsSMS, setNeedsSMS] = useState(false);
const [phoneCode, setPhoneCode] = useState("");
const router = useRouter();
// start the sign In process.
const handleSubmit = async (e) => {
e.preventDefault();
if (!isLoaded) {
return;
}
try {
const result = await signIn.create({
identifier: emailAddress,
password,
});
// this assumes the user doesn't have a 2FA method enabled. So handle that how you want
if (result.status === "complete") {
console.log(result);
await setActive({ session: result.createdSessionId });
router.push("/")
}
// Check if we need the second factor
if (result.status === "needs_second_factor") {
// Check if totp is enabled
const totpOn = signIn.supportedSecondFactors.find(
(f) => f.strategy === "totp",
);
if (totpOn) {
// show new UI
setNeedsVerify(true);
} else {
console.error("totp is not enabled for this user")
// go to phone and get phoneID
const phoneNumber = result.supportedSecondFactors.find(
(f) => f.strategy === "phone_code",
// This cast shouldn't be necessary but because TypeScript is dumb and can't infer it.
) as { phoneNumberId: string } | undefined;
// phone number found.
if (phoneNumber) {
setNeedsSMS(true);
// send text
result.prepareSecondFactor({
strategy: "phone_code",
phoneNumberId: phoneNumber.phoneNumberId
})
}
}
}
} catch (err: any) {
console.error("error", err.errors[0].longMessage)
}
};
const totpInput = async (e) => {
e.preventDefault();
const totpResult = await signIn?.attemptSecondFactor({
strategy: "totp",
code: totp
})
if (totpResult?.status === "complete") {
await setActive({ session: totpResult.createdSessionId });
router.push("/")
}
}
const smsInput = async (e) => {
e.preventDefault();
const totpResult = await signIn?.attemptSecondFactor({
strategy: "phone_code",
code: phoneCode
})
if (totpResult?.status === "complete") {
await setActive({ session: totpResult.createdSessionId });
router.push("/")
}
}
return (
<>
{!needsVerify && !needsSMS && (
<div>
<form>
<div>
<label htmlFor="email">Email</label>
<input onChange={(e) => setEmailAddress(e.target.value)} id="email" name="email" type="email" />
</div>
<div>
<label htmlFor="password">Password</label>
<input onChange={(e) => setPassword(e.target.value)} id="password" name="password" type="password" />
</div>
<button onClick={handleSubmit}>Sign In</button>
</form>
</div>
)}
{needsVerify && (
<div>
<form>
<div>
<label htmlFor="totp">ToTp</label>
<input onChange={(e) => setTotp(e.target.value)} id="totp" name="totp" type="text" />
</div>
<button onClick={totpInput}>Verify MFA Code</button>
</form>
</div>
)}
{needsSMS && (
<div>
<form>
<div>
<label htmlFor="phone">SMS CODE</label>
<input onChange={(e) => setPhoneCode(e.target.value)} id="phone" name="phone" type="text" />
</div>
<button onClick={smsInput}>Verify SMS Code</button>
</form>
</div>
)}
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment