edgahan
12/26/2022, 1:28 PMCaddy Server
to setup Next and Nest with proper SSL/Domains. You kinda make your life easier with this because of the cookies/ssl stuff.
Could be useful to know, just make a Caddyfile
like this and run caddy start
api.example.localhost {
reverse_proxy localhost:3001
}
web.example.localhost {
reverse_proxy localhost:3000
}
NextJS only as a 'frontend' (with SSR). So I'm not using the serverless functions or API functionalities.
NestJS is the API backend.
As per the Caddyfile above, both on the same main domain as api
and web
./app/signin/page.tsx
- This page renders the correct LoginForm (Enter email, Phone Number, SMS OTP, etc). Logic below.
- /app/signin/verify/page.tsx
- This page verifies the magic link clicked via email. It can also send out SMS codes automatically. Info below.
The flow is:
- User enters an email on /signin
route. At this point /app/signin/page.tsx
is rendering the 'Enter email' form.
- Magic link is sent via email
- Magic link is clicked and user is lands on /signin/verify
page w/ query params.
- On this page, inside a useEffect
we:
- await consumePasswordlessCode()
- If consume password code is successful we: await Passwordless.clearLoginAttemptInfo()
- If there is a phone number in await Session.getAccessTokenPayloadSecurely()
then send out a code via await createCode
.
- Either way, redirect back to /signin
Passwordless.init
twice to the receipe list but that gave an error that I couldn't use it twice. Sure there's an easy way around this, but instead I just did:
ThirdPartyPasswordless.init({
flowType: "MAGIC_LINK",
contactMethod: "EMAIL"
}),
Passwordless.init({
flowType: "USER_INPUT_CODE",
contactMethod: "PHONE",
override: {...}, // from the MFA docs
...
})
UserMetadata.init()
in the recipe list. The docs say it twice.
- With the Next 13 /app
directory it's SSR. That means to get SuperTokens.init
working I had to put it in a component with use client
at the top. I just made the component return null here, I guess.
App Layout:
<body>
<SuperTokensInit />
{children}
</body>
And then the SuperTokensInit Component itself looks like:
"use client";
import SuperTokens from 'supertokens-web-js';
import Session from 'supertokens-web-js/recipe/session';
import ThirdPartyPasswordless from 'supertokens-web-js/recipe/thirdpartypasswordless'
SuperTokens.init({
appInfo: {
appName: "AppName",
apiDomain: "https://api.example.localhost",
apiBasePath: "/auth",
},
recipeList: [
ThirdPartyPasswordless.init(),
Session.init()
]
});
export default function SuperTokensInit() {
return (
null
);
}
https://supertokens.com/docs/mfa/frontend-custom
(using a custom UI) and https://supertokens.com/docs/mfa/pre-built-ui/showing-login-ui
(Using the prebuilt UI) because the prebuilt UI pages have more info, e.g. how to collect OTP.
- On the user management page you will see two users - one with the phone number and one with the email. They will be linked. (It would be slightly nicer to only see one user here)
- Logic for which LoginForm to display (obviously can have better variable names)
- This will basically render one of:
- Email Form
- Phone Number Form
- SMS OTP FormuseEffect(() => {
const getLoginComponent = async () => {
if (!(await Session.doesSessionExist())) {
// Show Email Form
// If session doesn't exist, show first factor form
setForm('firstFactorEmailLoginForm')
} else if ((await getLoginAttemptInfo() !== undefined)) {
// Show SMS OPT Form
// The session DOES exist. The session will only exist is /verify flow is finished.
// And we have Login Attempt Info from 2nd factor (phone screen)
setForm('secondFactorSmsOtpForm')
} else if (!(await Session.getClaimValue({ claim: SecondFactorClaim }))) {
// Show SMS OPT Form or Show Phone Number Form
// Session exists and we have login attempt info
// We do not have claim value from the second factor.
// If we have a phone number from prev. flow, show OTP screen.
// For this to work we had to create a code on the `/signin/verify` page if `consumePasswordlessCode` worked.
/*
await createCode({
phoneNumber: accessTokenPayload.phoneNumber
});
*/
const accessTokenPayload = await Session.getAccessTokenPayloadSecurely();
if (accessTokenPayload.phoneNumber === undefined) {
setForm('secondFactorPhoneNumberForm')
} else {
setForm('secondFactorSmsOtpForm')
}
} else {
// 2FA is finished
// We finished first and second factor, everything should be good to go.
// You probably would have had to overwrite `consumeCodePOST` in the `supertokens.service.ts` file so that you create a record in your own DB.
// After that you can query your own API `/users/me` or something and redirect based on onboarding state.
redirectBasedOnUser()
}
}
getLoginComponent()
}, [])
rp
12/26/2022, 1:42 PM