Changes to email password flow
We start by disabling the public facing sign up API on the backend:
- NodeJS
- GoLang
- Python
- Other Frameworks
Important
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, InputAppInfo
from supertokens_python.recipe import emailpassword
from 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
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
(window as any).supertokensUIInit("supertokensui", {
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
style: `
[data-supertokens~=authPage] [data-supertokens~=headerSubtitle] {
display: none;
}
`,
recipeList: [ /* ... */]
});
import SuperTokens from "supertokens-auth-react";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
style: `
[data-supertokens~=authPage] [data-supertokens~=headerSubtitle] {
display: none;
}
`,
recipeList: [ /* ... */]
});
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
(window as any).supertokensUIInit("supertokensui", {
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
style: `
[data-supertokens~=authPage] [data-supertokens~=headerSubtitle] {
display: none;
}
`,
recipeList: [ /* ... */]
});
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
- Other Frameworks
Important
- Express
- Hapi
- Fastify
- Koa
- Loopback
- AWS Lambda / Netlify
- Next.js (Pages Dir)
- Next.js (App Dir)
- 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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
// 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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
res.status(200).json({ message: 'Success' })
}
import { NextResponse, NextRequest } from "next/server";
import SuperTokens from "supertokens-node";
import { withSession } from "supertokens-node/nextjs";
import UserRoles from "supertokens-node/recipe/userroles";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import { backendConfig } from "@/app/config/backend";
SuperTokens.init(backendConfig());
const FAKE_PASSWORD = "asokdA87fnf30efjoiOI**cwjkn";
export function POST(request: NextRequest) {
return withSession(request, async (err, session) => {
if (err) {
return NextResponse.json(err, { status: 500 });
}
const body = await request.json();
let email = body.email;
let signUpResult = await EmailPassword.signUp("public", email, FAKE_PASSWORD);
if (signUpResult.status === "EMAIL_ALREADY_EXISTS_ERROR") {
return NextResponse.json({ message: 'User already exists' }, { status: 400 });
}
// we successfully created the user. Now we should send them their invite link
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
return NextResponse.json({ message: 'Success' })
}, {
overrideGlobalClaimValidators: async function (globalClaimValidators) {
return [...globalClaimValidators, UserRoles.UserRoleClaim.validators.includes("admin")]
}
});
}
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("public", 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
await EmailPassword.sendResetPasswordEmail("public", signUpResult.user.id, email);
// TODO: send 200 response to the client
}
}
- Chi
- net/http
- Gin
- Mux
import (
"net/http"
"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("public", 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
_, err = emailpassword.SendResetPasswordEmail("public", signUpResult.OK.User.ID)
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/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 gin
func 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("public", 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
_, err = emailpassword.SendResetPasswordEmail("public", signUpResult.OK.User.ID)
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/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("public", 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
_, err = emailpassword.SendResetPasswordEmail("public", signUpResult.OK.User.ID)
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/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("public", 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
_, err = emailpassword.SendResetPasswordEmail("public", signUpResult.OK.User.ID)
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_session
from supertokens_python.recipe.session import SessionContainer
from fastapi import Depends
from supertokens_python.recipe.userroles import UserRoleClaim
from supertokens_python.recipe.emailpassword.asyncio import (
sign_up,
send_reset_password_email,
)
from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult
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("public", email, FAKE_PASSWORD)
if not isinstance(sign_up_result, SignUpOkResult):
# TODO: send 400 response to client
return
# we successfully created the user. Now we should send them their invite link
await send_reset_password_email("public", sign_up_result.user.id, email)
# TODO: send 200 responspe to client
from supertokens_python.recipe.session.framework.flask import verify_session
from supertokens_python.recipe.userroles import UserRoleClaim
from supertokens_python.recipe.emailpassword.syncio import (
sign_up,
send_reset_password_email,
)
from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult
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("public", email, FAKE_PASSWORD)
if not isinstance(sign_up_result, SignUpOkResult):
# TODO: send 400 response to client
return
# we successfully created the user. Now we should send them their invite link
send_reset_password_email("public", sign_up_result.user.id, email)
# TODO: send 200 responspe to client
from supertokens_python.recipe.session.framework.django.asyncio import verify_session
from django.http import HttpRequest
from supertokens_python.recipe.userroles import UserRoleClaim
from supertokens_python.recipe.emailpassword.asyncio import (
sign_up,
send_reset_password_email,
)
from supertokens_python.recipe.emailpassword.interfaces import SignUpOkResult
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("public", email, FAKE_PASSWORD)
if not isinstance(sign_up_result, SignUpOkResult):
# TODO: send 400 response to client
return
# we successfully created the user. Now we should send them their invite link
await send_reset_password_email("public", sign_up_result.user.id, email)
# 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.
- If you only want to create the reset password link and send it yourself, you can use the
createResetPasswordLink
function instead which will return the link as a string.
Multi Tenancy
For a multi tenant use case, you can pass in a tenantId when creating a user via the signUp
function call. This will ensure that the user can only sign into that tenant. In the code above, we pass in the "public"
tenantId, which is the default tenantId.
You will also need to pass in the same tenantId to the sendResetPasswordEmail
function. This will add the tenantId to the reset link and also ensure that the link has the correct value for the domain based on the tenant ID (assuming that you have overriden the sendEmail
function to change the domain in the link).
In case you are using createResetPasswordLink
and sending the link yourself, the domain on that link will be the one that's configured in the appInfo
object in supertokens.init
, so you will need to change the link's domain to the one that belongs to that tenantId.
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
- Other Frameworks
Important
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 or in the password reset flow
if (input.password === FAKE_PASSWORD) {
throw new Error("Use a different password")
}
return originalImplementation.updateEmailOrPassword(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
ogEmailPasswordSignIn := *originalImplementation.SignIn
ogUpdateEmailOrPassword := *originalImplementation.UpdateEmailOrPassword
(*originalImplementation.UpdateEmailOrPassword) = func(userId string, email, password *string, applyPasswordPolicy *bool, tenantId 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, applyPasswordPolicy, tenantId, userContext)
}
(*originalImplementation.ResetPasswordUsingToken) = func(token, newPassword, tenantId 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, tenantId, userContext)
}
(*originalImplementation.SignIn) = func(email, password, tenantId 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 ogEmailPasswordSignIn(email, password, tenantId, userContext)
}
return originalImplementation
},
},
}),
},
})
}
from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import emailpassword
from supertokens_python.recipe.emailpassword.interfaces import (
APIInterface,
RecipeInterface,
WrongCredentialsError,
)
from supertokens_python.types import RecipeUserId
from supertokens_python.recipe.session import SessionContainer
from 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_emailpassword_sign_in = original_impl.sign_in
og_update_email_or_password = original_impl.update_email_or_password
async def update_email_or_password(
recipe_user_id: RecipeUserId,
email: Union[str, None],
password: Union[str, None],
apply_password_policy: Union[bool, None],
tenant_id_for_password_policy: str,
user_context: Dict[str, Any],
):
# This can be called on the backend
# in your own APIs or in the password reset flow
if password == FAKE_PASSWORD:
raise Exception("Please use a different password")
return await og_update_email_or_password(
recipe_user_id,
email,
password,
apply_password_policy,
tenant_id_for_password_policy,
user_context,
)
async def emailpassword_sign_in(
email: str,
password: str,
tenant_id: str,
session: Union[SessionContainer, None],
should_try_linking_with_session_user: Union[bool, None],
user_context: Dict[str, Any],
):
# This is called in the email password sign in API
if password == FAKE_PASSWORD:
return WrongCredentialsError()
return await og_emailpassword_sign_in(
email,
password,
tenant_id,
session,
should_try_linking_with_session_user,
user_context,
)
original_impl.update_email_or_password = update_email_or_password
original_impl.sign_in = emailpassword_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
),
)
],
)