-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Passkeys SDK UI Recipe and Page (#161)
* Add passkeys SDK UI recipe and page * Add views enum logic & success view * Add views enum logic & success view
- Loading branch information
1 parent
251925b
commit 6b660e5
Showing
8 changed files
with
360 additions
and
253 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import React from 'react'; | ||
import {OTPMethods, Products, StytchError, StytchEvent, StytchLoginConfig} from '@stytch/vanilla-js'; | ||
import {StytchLogin, useStytchUser} from '@stytch/nextjs'; | ||
import {useRouter} from 'next/router'; | ||
|
||
const loginConfig: StytchLoginConfig = { | ||
sessionOptions: { | ||
sessionDurationMinutes: 60, | ||
}, | ||
products: [Products.passkeys, Products.otp], | ||
otpOptions: { | ||
expirationMinutes: 10, | ||
methods: [OTPMethods.Email], | ||
}, | ||
}; | ||
|
||
const callbackConfig = { | ||
onEvent: (message: StytchEvent) => console.log(message), | ||
onError: (error: StytchError) => console.log(error), | ||
} | ||
|
||
const LoginWithPasskeys = () => { | ||
const { user } = useStytchUser(); | ||
const router = useRouter(); | ||
|
||
if (user) { | ||
router.push('/recipes/passkeys/profile'); | ||
} | ||
|
||
return <StytchLogin config={loginConfig} callbacks={callbackConfig} />; | ||
}; | ||
|
||
export default LoginWithPasskeys; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import React, {useEffect, useState} from 'react'; | ||
import {AuthenticationFactor, Products, StytchError, StytchEvent, StytchEventType} from '@stytch/vanilla-js'; | ||
import {StytchPasskeyRegistration, useStytch, useStytchSession, useStytchUser} from '@stytch/nextjs'; | ||
|
||
const styles: Record<string, React.CSSProperties> = { | ||
registerButton: { | ||
margin: 'auto' | ||
} | ||
}; | ||
|
||
enum StepUpType { | ||
email = "email", | ||
webauthn = "webauthn", | ||
} | ||
|
||
const StepUp = ({ type }: { type: StepUpType }) => { | ||
const [inputValue, setInputValue] = useState(""); | ||
const [methodID, setMethodID] = useState(""); | ||
const { user } = useStytchUser(); | ||
const [error, setError] = useState(""); | ||
const stytch = useStytch(); | ||
|
||
const validateOTPButtonClick = () => { | ||
stytch.otps.authenticate(inputValue, methodID, { | ||
session_duration_minutes: 30, | ||
}).catch((e) => { | ||
setError("Error occurred validating OTP: " + e); | ||
}); | ||
}; | ||
|
||
const handleSendOTPButtonClick = () => { | ||
stytch.otps.email | ||
.send(user?.emails?.at(0)?.email as string, { | ||
expiration_minutes: 5, | ||
}) | ||
.then((resp) => { | ||
setMethodID(resp.method_id); | ||
}) | ||
.catch((e) => { | ||
setError("Error occurred sending email: " + e); | ||
}); | ||
}; | ||
|
||
const handleInputChange = (event: any) => { | ||
setInputValue(event.target.value); | ||
}; | ||
|
||
if (type === StepUpType.webauthn) { | ||
return ( | ||
<> | ||
<h3>You need to step up {type} before creating Passkeys!</h3> | ||
<button | ||
color="primary" | ||
onClick={() => { | ||
stytch.webauthn.authenticate({ | ||
session_duration_minutes: 30, | ||
}); | ||
}} | ||
> | ||
Step Up WebAuthn | ||
</button> | ||
</> | ||
); | ||
} | ||
|
||
return ( | ||
<div> | ||
<h3>You need to step up {type} before creating Passkeys!</h3> | ||
<button | ||
className="mt2" | ||
onClick={handleSendOTPButtonClick} | ||
> | ||
Send OTP to{" "} | ||
{user?.emails?.at(0)?.email as string} | ||
</button> | ||
<br/> | ||
<br/> | ||
<input | ||
placeholder="123456" | ||
type="text" | ||
value={inputValue} | ||
onChange={handleInputChange} | ||
/> | ||
<br/> | ||
<button | ||
className="mt2" | ||
color="primary" | ||
onClick={validateOTPButtonClick} | ||
> | ||
Validate OTP | ||
</button> | ||
{error} | ||
</div> | ||
); | ||
}; | ||
|
||
|
||
enum PasskeyRegViews { | ||
Start = "START", | ||
Register = "REGISTER", | ||
Success = "SUCCESS", | ||
StepUpWebAuthn = "STEP_UP_WEBAUTHN", | ||
StepUpEmail = "STEP_UP_EMAIL", | ||
} | ||
|
||
const PasskeyRegistration = () => { | ||
const [displayView, setDisplayView] = useState(PasskeyRegViews.Start); | ||
const { session } = useStytchSession(); | ||
const { user, isInitialized } = useStytchUser(); | ||
|
||
useEffect(() => { | ||
const sessionHasPasskeyFactor = session?.authentication_factors?.some( | ||
(factor: AuthenticationFactor) => factor.delivery_method === "webauthn_registration", | ||
); | ||
const sessionHasEmailFactor = session?.authentication_factors?.some( | ||
(factor: AuthenticationFactor) => factor.delivery_method === "email", | ||
); | ||
const displayPasskeyStepUp = sessionHasEmailFactor && !sessionHasPasskeyFactor && user?.webauthn_registrations?.length! > 0; | ||
const displayEmailStepUp = !sessionHasEmailFactor && sessionHasPasskeyFactor; | ||
if (displayEmailStepUp) { | ||
setDisplayView(PasskeyRegViews.StepUpEmail); | ||
} else if (displayPasskeyStepUp){ | ||
setDisplayView(PasskeyRegViews.StepUpWebAuthn); | ||
} | ||
|
||
// If the user authenticates succesfully on the step-up page we should navigate to the registration view | ||
if (displayView === PasskeyRegViews.StepUpEmail || displayView === PasskeyRegViews.StepUpWebAuthn | ||
&& !displayEmailStepUp && !displayPasskeyStepUp) { | ||
setDisplayView(PasskeyRegViews.Register); | ||
} | ||
|
||
},[session, user]); | ||
|
||
const callbackConfig = { | ||
onEvent: (message: StytchEvent) => { | ||
console.log(message) | ||
if (message.type === StytchEventType.PasskeySkip) { | ||
alert("We just return to the start here, but you can do whatever you want!"); | ||
setDisplayView(PasskeyRegViews.Start); | ||
} | ||
if (message.type === StytchEventType.PasskeyDone) { | ||
setDisplayView(PasskeyRegViews.Success); | ||
} | ||
}, | ||
onError: (error: StytchError) => console.log(error), | ||
} | ||
|
||
return ( | ||
<> | ||
{displayView === PasskeyRegViews.Start && ( | ||
<button style={styles.registerButton} onClick={() => setDisplayView(PasskeyRegViews.Register)}> | ||
Register a Passkey | ||
</button> | ||
)} | ||
{displayView === PasskeyRegViews.StepUpWebAuthn && ( | ||
<StepUp type={StepUpType.webauthn} /> | ||
)} | ||
{displayView === PasskeyRegViews.StepUpEmail && ( | ||
<StepUp type={StepUpType.email} /> | ||
)} | ||
{displayView === PasskeyRegViews.Register && ( | ||
<StytchPasskeyRegistration | ||
styles={{ container: { width: "400px" } }} | ||
config={{ products: [Products.passkeys]}} | ||
callbacks={callbackConfig} | ||
/> | ||
)} | ||
{displayView === PasskeyRegViews.Success && ( | ||
<div> | ||
<h3>Passkey created!</h3> | ||
<p> | ||
You can now use your Passkey to sign in to your account. | ||
</p> | ||
<button | ||
className="mt2" | ||
onClick={() => { | ||
setDisplayView(PasskeyRegViews.Register); | ||
}}> | ||
Register Another Passkey | ||
</button> | ||
</div> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default PasskeyRegistration; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
6b660e5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
stytch-nextjs-integration – ./
stytch-nextjs-integration.vercel.app
stytchdemo.com
stytch-nextjs-integration-git-main-stytch-auth.vercel.app
stytch-nextjs-integration-stytch-auth.vercel.app
www.stytchdemo.com