Which frontend SDK do you use?
supertokens-web-js / mobile
supertokens-auth-react
Changes to email password flow
We start by disabling the public facing sign up API on the backend:
- NodeJS
- GoLang
- Python
import SuperTokens from "supertokens-node";import EmailPassword from "supertokens-node/recipe/emailpassword";
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, supertokens: { connectionURI: "...", }, recipeList: [ EmailPassword.init({ override: { apis: (originalImplementation) => { return { ...originalImplementation, signUpPOST: undefined, } } } }) ]});
import ( "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels" "github.com/supertokens/supertokens-golang/supertokens")
func main() { supertokens.Init(supertokens.TypeInput{ RecipeList: []supertokens.Recipe{ emailpassword.Init(&epmodels.TypeInput{ Override: &epmodels.OverrideStruct{ APIs: func(originalImplementation epmodels.APIInterface) epmodels.APIInterface { originalImplementation.SignUpPOST = nil return originalImplementation }, }, }), }, })}
from supertokens_python import init, InputAppInfofrom supertokens_python.recipe import emailpasswordfrom supertokens_python.recipe.emailpassword.interfaces import APIInterface
def apis_override(original_impl: APIInterface): original_impl.disable_sign_up_post = True return original_impl
init( app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."), framework='...', recipe_list=[ emailpassword.init( override=emailpassword.InputOverrideConfig( apis=apis_override ), ) ])
The next step is to disable the sign up button on the sign in form. This can be done by using CSS to hide the sign up button:
- ReactJS
- Angular
- Vue
import SuperTokens from "supertokens-auth-react";import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ EmailPassword.init({ signInAndUpFeature: { signInForm: { style: { "headerSubtitle": { display: "none" } }, } }, }), ]});
/app/auth/supertokensAuthComponent.tsx
import SuperTokens from "supertokens-auth-react";import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ EmailPassword.init({ signInAndUpFeature: { signInForm: { style: { "headerSubtitle": { display: "none" } }, } }, }), ]});
/components/Supertokens.tsx
import SuperTokens from "supertokens-auth-react";import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, recipeList: [ EmailPassword.init({ signInAndUpFeature: { signInForm: { style: { "headerSubtitle": { display: "none" } }, } }, }), ]});
To create email password users, you will need to make an API on your backend which will:
- Call the
signUp
function from our backend SDK using that user's email and a fake password. This fake password should be unguessable and should be shared across all invited users. - Generate a password reset link and send that as an invite link to the user's email.
- In the code below, we also make sure that the user who calls this API has the
admin
role - but you can change this part to whatever you like.
Once the user clicks the link, they will be shown a page asking them to input their password after which, they can login.
- NodeJS
- GoLang
- Python
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js
- NestJS
import express from "express";import { verifySession } from "supertokens-node/recipe/session/framework/express";import { SessionRequest } from "supertokens-node/framework/express";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
let app = express();
app.post("/create-user", verifySession({ overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] }}), async (req: SessionRequest, res) => { let email = req.body.email;
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { res.status(400).send("User already exists"); return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); res.send("Success");});
import Hapi from "@hapi/hapi";import { verifySession } from "supertokens-node/recipe/session/framework/hapi";import { SessionRequest } from "supertokens-node/framework/hapi";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
let server = Hapi.server({ port: 8000 });
server.route({ path: "/create-user", method: "post", options: { pre: [ { method: verifySession({ overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] } }) }, ], }, handler: async (req: SessionRequest, res) => { let email = (req.payload.valueOf() as any).email;
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { res.response("User already exists").code(400); return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); res.response("Success").code(200); }})
import Fastify from "fastify";import { verifySession } from "supertokens-node/recipe/session/framework/fastify";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
let fastify = Fastify();
fastify.post("/create-user", { preHandler: verifySession({ overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] } }),}, async (req, res) => { let email = req.body.email;
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { res.code(400).send("User already exists"); return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); res.code(200).send("Success");});
import { verifySession } from "supertokens-node/recipe/session/framework/awsLambda";import { SessionEventV2 } from "supertokens-node/framework/awsLambda";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
async function createUser(awsEvent: SessionEventV2) { let email = JSON.parse(awsEvent.body!).email;
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { return { statusCode: '400', body: "User already exists" } }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); return { statusCode: '200', body: "Success" }};
exports.handler = verifySession(createUser, { overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] }});
import KoaRouter from "koa-router";import { verifySession } from "supertokens-node/recipe/session/framework/koa";import { SessionContext } from "supertokens-node/framework/koa";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
let router = new KoaRouter();
router.post("/create-user", verifySession({ overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] }}), async (ctx: SessionContext, next) => { let email = (ctx.body as any).email;
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { ctx.status = 400; ctx.body = "User already exists"; return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); ctx.status = 200; ctx.body = "Success";});
import { inject, intercept } from "@loopback/core";import { RestBindings, MiddlewareContext, post, response } from "@loopback/rest";import { verifySession } from "supertokens-node/recipe/session/framework/loopback";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
class LikeComment { constructor(@inject(RestBindings.Http.CONTEXT) private ctx: MiddlewareContext) { } @post("/create-user") @intercept(verifySession({ overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] } })) async handler() { let email = "" // TODO: get from request body
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { // TODO: send 400 response to the client. return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); // TODO: send 200 response to the client }}
import { superTokensNextWrapper } from 'supertokens-node/nextjs'import { verifySession } from "supertokens-node/recipe/session/framework/express";import { SessionRequest } from "supertokens-node/framework/express";import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
export default async function createUser(req: SessionRequest, res: any) { await superTokensNextWrapper( async (next) => { await verifySession({ overrideGlobalClaimValidators: async function (globalClaimValidators) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] } })(req, res, next); }, req, res )
let email = req.body.email;
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { res.status(400).json({ message: 'User already exists' }) return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); res.status(200).json({ message: 'Success' })}
import { Controller, Post, UseGuards, Session } from "@nestjs/common";import { SessionContainer } from "supertokens-node/recipe/session";import { AuthGuard } from './auth/auth.guard';import UserRoles from "supertokens-node/recipe/userroles";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
@Controller()export class CreateUserController { @Post('create-user') @UseGuards(new AuthGuard({ overrideGlobalClaimValidators: async function (globalClaimValidators: any) { return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")] } })) // For more information about this guard please read our NestJS guide. async postAPI(@Session() session: SessionContainer): Promise<void> { let email = "" // TODO: get from request body
let signUpResult = await EmailPassword.signUp(email, FAKE_PASSWORD); if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") { // TODO: send 400 response to the client. return; }
// we successfully created the user. Now we should send them their invite link let passwordResetToken = await EmailPassword.createResetPasswordToken(signUpResult.user.id);
if (passwordResetToken.status === "UNKNOWN_USER_ID_ERROR") { throw new Error("Should never come here"); }
let inviteLink = "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.token await EmailPassword.sendEmail({ type: "PASSWORD_RESET", passwordResetLink: inviteLink, user: { email: signUpResult.user.email, id: signUpResult.user.id } }); // TODO: send 200 response to the client }}
- Chi
- net/http
- Gin
- Mux
import ( "net/http"
"github.com/supertokens/supertokens-golang/ingredients/emaildelivery" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/claims" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims" "github.com/supertokens/supertokens-golang/supertokens")
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
func main() { _ = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
session.VerifySession(&sessmodels.VerifySessionOptions{ OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { globalClaimValidators = append(globalClaimValidators, userrolesclaims.PermissionClaimValidators.Includes("admin", nil, nil)) return globalClaimValidators, nil }, }, createUserAPI).ServeHTTP(rw, r) })}
func createUserAPI(w http.ResponseWriter, r *http.Request) { email := "" // TODO: read email from request body
signUpResult, err := emailpassword.SignUp(email, FAKE_PASSWORD) if err != nil { // TODO: send 500 to the client return }
if signUpResult.EmailAlreadyExistsError != nil { // TODO: send 400 to the client return }
// we successfully created the user. Now we should send them their invite link passwordResetToken, err := emailpassword.CreateResetPasswordToken(signUpResult.OK.User.ID) if err != nil { // TODO: send 500 to the client return }
inviteLink := "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.OK.Token err = emailpassword.SendEmail(emaildelivery.EmailType{ PasswordReset: &emaildelivery.PasswordResetType{ User: emaildelivery.User{ ID: signUpResult.OK.User.ID, Email: signUpResult.OK.User.Email, }, PasswordResetLink: inviteLink, }, }) if err != nil { // TODO: send 500 to the client return } // TODO: send 200 to the client}
import ( "net/http"
"github.com/gin-gonic/gin" "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/claims" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims" "github.com/supertokens/supertokens-golang/supertokens")
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
func main() { router := gin.New()
// Wrap the API handler in session.VerifySession router.POST("/create-user", verifySession(&sessmodels.VerifySessionOptions{ OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { globalClaimValidators = append(globalClaimValidators, userrolesclaims.PermissionClaimValidators.Includes("admin", nil, nil)) return globalClaimValidators, nil }, }), createUserAPI)}
// This is a function that wraps the supertokens verification function// to work the ginfunc verifySession(options *sessmodels.VerifySessionOptions) gin.HandlerFunc { return func(c *gin.Context) { session.VerifySession(options, func(rw http.ResponseWriter, r *http.Request) { c.Request = c.Request.WithContext(r.Context()) c.Next() })(c.Writer, c.Request) // we call Abort so that the next handler in the chain is not called, unless we call Next explicitly c.Abort() }}
func createUserAPI(c *gin.Context) { email := "" // TODO: read email from request body
signUpResult, err := emailpassword.SignUp(email, FAKE_PASSWORD) if err != nil { // TODO: send 500 to the client return }
if signUpResult.EmailAlreadyExistsError != nil { // TODO: send 400 to the client return }
// we successfully created the user. Now we should send them their invite link passwordResetToken, err := emailpassword.CreateResetPasswordToken(signUpResult.OK.User.ID) if err != nil { // TODO: send 500 to the client return }
inviteLink := "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.OK.Token err = emailpassword.SendEmail(emaildelivery.EmailType{ PasswordReset: &emaildelivery.PasswordResetType{ User: emaildelivery.User{ ID: signUpResult.OK.User.ID, Email: signUpResult.OK.User.Email, }, PasswordResetLink: inviteLink, }, }) if err != nil { // TODO: send 500 to the client return } // TODO: send 200 to the client}
import ( "net/http"
"github.com/go-chi/chi" "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/claims" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims" "github.com/supertokens/supertokens-golang/supertokens")
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
func main() { r := chi.NewRouter()
// Wrap the API handler in session.VerifySession r.Post("/create-user", session.VerifySession(&sessmodels.VerifySessionOptions{ OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { globalClaimValidators = append(globalClaimValidators, userrolesclaims.PermissionClaimValidators.Includes("admin", nil, nil)) return globalClaimValidators, nil }, }, createUserAPI))}
func createUserAPI(w http.ResponseWriter, r *http.Request) { email := "" // TODO: read email from request body
signUpResult, err := emailpassword.SignUp(email, FAKE_PASSWORD) if err != nil { // TODO: send 500 to the client return }
if signUpResult.EmailAlreadyExistsError != nil { // TODO: send 400 to the client return }
// we successfully created the user. Now we should send them their invite link passwordResetToken, err := emailpassword.CreateResetPasswordToken(signUpResult.OK.User.ID) if err != nil { // TODO: send 500 to the client return }
inviteLink := "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.OK.Token err = emailpassword.SendEmail(emaildelivery.EmailType{ PasswordReset: &emaildelivery.PasswordResetType{ User: emaildelivery.User{ ID: signUpResult.OK.User.ID, Email: signUpResult.OK.User.Email, }, PasswordResetLink: inviteLink, }, }) if err != nil { // TODO: send 500 to the client return } // TODO: send 200 to the client}
import ( "net/http"
"github.com/gorilla/mux" "github.com/supertokens/supertokens-golang/ingredients/emaildelivery" "github.com/supertokens/supertokens-golang/recipe/session" "github.com/supertokens/supertokens-golang/recipe/session/claims" "github.com/supertokens/supertokens-golang/recipe/session/sessmodels" "github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/userroles/userrolesclaims" "github.com/supertokens/supertokens-golang/supertokens")
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
func main() { router := mux.NewRouter()
// Wrap the API handler in session.VerifySession router.HandleFunc("/create-user", session.VerifySession(&sessmodels.VerifySessionOptions{ OverrideGlobalClaimValidators: func(globalClaimValidators []claims.SessionClaimValidator, sessionContainer sessmodels.SessionContainer, userContext supertokens.UserContext) ([]claims.SessionClaimValidator, error) { globalClaimValidators = append(globalClaimValidators, userrolesclaims.PermissionClaimValidators.Includes("admin", nil, nil)) return globalClaimValidators, nil }, }, createUserAPI)).Methods(http.MethodPost)}
func createUserAPI(w http.ResponseWriter, r *http.Request) { email := "" // TODO: read email from request body
signUpResult, err := emailpassword.SignUp(email, FAKE_PASSWORD) if err != nil { // TODO: send 500 to the client return }
if signUpResult.EmailAlreadyExistsError != nil { // TODO: send 400 to the client return }
// we successfully created the user. Now we should send them their invite link passwordResetToken, err := emailpassword.CreateResetPasswordToken(signUpResult.OK.User.ID) if err != nil { // TODO: send 500 to the client return }
inviteLink := "http://localhost:3000/auth/reset-password?token=" + passwordResetToken.OK.Token err = emailpassword.SendEmail(emaildelivery.EmailType{ PasswordReset: &emaildelivery.PasswordResetType{ User: emaildelivery.User{ ID: signUpResult.OK.User.ID, Email: signUpResult.OK.User.Email, }, PasswordResetLink: inviteLink, }, }) if err != nil { // TODO: send 500 to the client return } // TODO: send 200 to the client}
- FastAPI
- Flask
- Django
from supertokens_python.recipe.session.framework.fastapi import verify_sessionfrom supertokens_python.recipe.session import SessionContainerfrom fastapi import Dependsfrom supertokens_python.recipe.userroles import UserRoleClaimfrom supertokens_python.recipe.emailpassword.asyncio import sign_up, create_reset_password_token, send_emailfrom supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError, CreateResetPasswordWrongUserIdErrorfrom supertokens_python.recipe.emailpassword.types import PasswordResetEmailTemplateVars, PasswordResetEmailTemplateVarsUser
FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
@app.post('/create-user') async def create_user(session: SessionContainer = Depends(verify_session( override_global_claim_validators=lambda global_validators, session, user_context: global_validators + [UserRoleClaim.validators.includes("admin")]))): email = "" # TODO: read from request body. sign_up_result = await sign_up(email, FAKE_PASSWORD)
if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): # TODO: send 400 response to client return
# we successfully created the user. Now we should send them their invite link password_reset_token = await create_reset_password_token(sign_up_result.user.user_id)
if isinstance(password_reset_token, CreateResetPasswordWrongUserIdError): raise Exception("Should never come here")
invite_link = "http://localhost:3000/auth/reset-password?token=" + \ password_reset_token.token
await send_email(input_=PasswordResetEmailTemplateVars(PasswordResetEmailTemplateVarsUser(sign_up_result.user.user_id, sign_up_result.user.email), invite_link))
# TODO: send 200 responspe to client
from supertokens_python.recipe.session.framework.flask import verify_sessionfrom supertokens_python.recipe.userroles import UserRoleClaimfrom supertokens_python.recipe.emailpassword.syncio import sign_up, create_reset_password_token, send_emailfrom supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError, CreateResetPasswordWrongUserIdErrorfrom supertokens_python.recipe.emailpassword.types import PasswordResetEmailTemplateVars, PasswordResetEmailTemplateVarsUser
FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
@app.route('/create_user', methods=['POST']) @verify_session( override_global_claim_validators=lambda global_validators, session, user_context: global_validators + [UserRoleClaim.validators.includes("admin")])def create_user(): email = "" # TODO: read from request body. sign_up_result = sign_up(email, FAKE_PASSWORD)
if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): # TODO: send 400 response to client return
# we successfully created the user. Now we should send them their invite link password_reset_token = create_reset_password_token( sign_up_result.user.user_id)
if isinstance(password_reset_token, CreateResetPasswordWrongUserIdError): raise Exception("Should never come here")
invite_link = "http://localhost:3000/auth/reset-password?token=" + \ password_reset_token.token
send_email(input_=PasswordResetEmailTemplateVars(PasswordResetEmailTemplateVarsUser( sign_up_result.user.user_id, sign_up_result.user.email), invite_link))
# TODO: send 200 responspe to client
from supertokens_python.recipe.session.framework.django.asyncio import verify_sessionfrom django.http import HttpRequestfrom supertokens_python.recipe.userroles import UserRoleClaimfrom supertokens_python.recipe.emailpassword.asyncio import sign_up, create_reset_password_token, send_emailfrom supertokens_python.recipe.emailpassword.interfaces import SignUpEmailAlreadyExistsError, CreateResetPasswordWrongUserIdErrorfrom supertokens_python.recipe.emailpassword.types import PasswordResetEmailTemplateVars, PasswordResetEmailTemplateVarsUser
FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
@verify_session( override_global_claim_validators=lambda global_validators, session, user_context: global_validators + [UserRoleClaim.validators.includes("admin")])async def create_user(request: HttpRequest): email = "" # TODO: read from request body. sign_up_result = await sign_up(email, FAKE_PASSWORD)
if isinstance(sign_up_result, SignUpEmailAlreadyExistsError): # TODO: send 400 response to client return
# we successfully created the user. Now we should send them their invite link password_reset_token = await create_reset_password_token(sign_up_result.user.user_id)
if isinstance(password_reset_token, CreateResetPasswordWrongUserIdError): raise Exception("Should never come here")
invite_link = "http://localhost:3000/auth/reset-password?token=" + \ password_reset_token.token
await send_email(input_=PasswordResetEmailTemplateVars(PasswordResetEmailTemplateVarsUser(sign_up_result.user.user_id, sign_up_result.user.email), invite_link))
# TODO: send 200 responspe to client
- The code above uses the default password reset path for the invite link (
/auth/reset-password
). If you are using the pre built UI, this will show password reset page to the user. If you want to show a different UI to the user, then you can use a different path in the link and make your own UI on that path. If you are making your own UI, you can use the password reset functions provided by our frontend SDK to call the password reset token consumption API from the frontend. - The
sendEmail
function used above sends the default password reset email (or the one you customised using theemailDelivery
config). Instead, you can also send a different email to the user specifically for the invite flow. - You can change the lifetime of the password reset token, and therefore the invite link, by following this guide.
The final step is to:
- Override the
signIn
recipe function on the backend to reject sign in attempts which use the fake password. This is done so that if someone knows the fake password, they cannot sign in as the invited user before they reset their password. - Override the change password functions to prevent users from changing their password to the fake password.
- NodeJS
- GoLang
- Python
import SuperTokens from "supertokens-node";import EmailPassword from "supertokens-node/recipe/emailpassword";
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
SuperTokens.init({ appInfo: { apiDomain: "...", appName: "...", websiteDomain: "..." }, supertokens: { connectionURI: "...", }, recipeList: [ EmailPassword.init({ override: { apis: (originalImplementation) => { // ... override from previous code snippets... return originalImplementation }, functions: (originalImplementation) => { return { ...originalImplementation, updateEmailOrPassword: async function (input) { // This can be called on the backend // in your own APIs if (input.password === FAKE_PASSWORD) { throw new Error("Use a different password") } return originalImplementation.updateEmailOrPassword(input); }, resetPasswordUsingToken: async function (input) { // This is called during the password reset flow // when the user enters their new password if (input.newPassword === FAKE_PASSWORD) { return { status: "RESET_PASSWORD_INVALID_TOKEN_ERROR" } } return originalImplementation.resetPasswordUsingToken(input); }, signIn: async function (input) { // This is called in the email password sign in API if (input.password === FAKE_PASSWORD) { return { status: "WRONG_CREDENTIALS_ERROR" } } return originalImplementation.signIn(input); }, } } } }) ]});
import ( "errors"
"github.com/supertokens/supertokens-golang/recipe/emailpassword" "github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels" "github.com/supertokens/supertokens-golang/supertokens")
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
func main() { supertokens.Init(supertokens.TypeInput{ RecipeList: []supertokens.Recipe{ emailpassword.Init(&epmodels.TypeInput{ Override: &epmodels.OverrideStruct{ APIs: func(originalImplementation epmodels.APIInterface) epmodels.APIInterface { // ...from previous code snippets... return originalImplementation }, Functions: func(originalImplementation epmodels.RecipeInterface) epmodels.RecipeInterface { ogResetPasswordUsingToken := *originalImplementation.ResetPasswordUsingToken ogSignIn := *originalImplementation.SignIn ogUpdateEmailOrPassword := *originalImplementation.UpdateEmailOrPassword
(*originalImplementation.UpdateEmailOrPassword) = func(userId string, email, password *string, userContext supertokens.UserContext) (epmodels.UpdateEmailOrPasswordResponse, error) { // This can be called on the backend // in your own APIs if password != nil && *password == FAKE_PASSWORD { return epmodels.UpdateEmailOrPasswordResponse{}, errors.New("use a different password") } return ogUpdateEmailOrPassword(userId, email, password, userContext) }
(*originalImplementation.ResetPasswordUsingToken) = func(token, newPassword string, userContext supertokens.UserContext) (epmodels.ResetPasswordUsingTokenResponse, error) { // This is called during the password reset flow // when the user enters their new password if newPassword == FAKE_PASSWORD { return epmodels.ResetPasswordUsingTokenResponse{ ResetPasswordInvalidTokenError: &struct{}{}, }, nil } return ogResetPasswordUsingToken(token, newPassword, userContext) }
(*originalImplementation.SignIn) = func(email, password string, userContext supertokens.UserContext) (epmodels.SignInResponse, error) { // This is called in the email password sign in API if password == FAKE_PASSWORD { return epmodels.SignInResponse{ WrongCredentialsError: &struct{}{}, }, nil } return ogSignIn(email, password, userContext) }
return originalImplementation }, }, }), }, })}
from supertokens_python import init, InputAppInfofrom supertokens_python.recipe import emailpasswordfrom supertokens_python.recipe.emailpassword.interfaces import APIInterface, RecipeInterface, SignInOkResult, SignInWrongCredentialsError, ResetPasswordUsingTokenOkResult, ResetPasswordUsingTokenInvalidTokenError, UpdateEmailOrPasswordOkResult, UpdateEmailOrPasswordEmailAlreadyExistsError, UpdateEmailOrPasswordUnknownUserIdErrorfrom typing import Dict, Any, Union
FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn"
def apis_override(original_impl: APIInterface): # ... from previous code snippets... return original_impl
def functions_override(original_impl: RecipeInterface): og_sign_in = original_impl.sign_in og_update_email_or_password = original_impl.update_email_or_password og_reset_password_using_token = original_impl.reset_password_using_token
async def update_email_or_password( user_id: str, email: Union[str, None], password: Union[str, None], user_context: Dict[str, Any], ) -> Union[ UpdateEmailOrPasswordOkResult, UpdateEmailOrPasswordEmailAlreadyExistsError, UpdateEmailOrPasswordUnknownUserIdError, ]: # This can be called on the backend # in your own APIs if (password == FAKE_PASSWORD): raise Exception("Please use a different password") return await og_update_email_or_password(user_id, email, password, user_context)
async def reset_password_using_token( token: str, new_password: str, user_context: Dict[str, Any] ) -> Union[ ResetPasswordUsingTokenOkResult, ResetPasswordUsingTokenInvalidTokenError ]: # This is called during the password reset flow # when the user enters their new password if (new_password == FAKE_PASSWORD): return ResetPasswordUsingTokenInvalidTokenError() return await og_reset_password_using_token(token, new_password, user_context)
async def sign_in( email: str, password: str, user_context: Dict[str, Any] ) -> Union[SignInOkResult, SignInWrongCredentialsError]: # This is called in the email password sign in API if (password == FAKE_PASSWORD): return SignInWrongCredentialsError() return await og_sign_in(email, password, user_context)
original_impl.update_email_or_password = update_email_or_password original_impl.reset_password_using_token = reset_password_using_token original_impl.sign_in = sign_in return original_impl
init( app_info=InputAppInfo( api_domain="...", app_name="...", website_domain="..."), framework='...', recipe_list=[ emailpassword.init( override=emailpassword.InputOverrideConfig( apis=apis_override, functions=functions_override ), ) ])