1. Project Overview & Quickstart (supertokens/supertokens-core)
Open-Source auth provider
Add secure login and session management to your apps. SDKs available
for popular languages and front-end frameworks e.g. Node.js, Go, Python, React.js, React Native, Vanilla JS, etc.
Supertokens architecture is optimized to add secure authentication for your users without compromising on user and
developer experience
Three building blocks of SuperTokens architecture
- Frontend SDK: Manages session tokens and renders login UI widgets
- Backend SDK: Provides APIs for sign-up, sign-in, signout, session refreshing, etc. Your Frontend will talk to these
APIs - SuperTokens Core: The HTTP service for the core auth logic and database operations. This service is used by the
Backend SDK
Features
- Passwordless Login
- Social Login
- Email Password Login
- Phone Password Login
- Session Management
- Multi-Factor Authentication
- Multi Tenancy / Organization Support (Enterprise SSO)
- User Roles
- Microservice Authentication
Learn more
- ๐ What is SuperTokens?
- ๐๏ธ Architecture
- โ Why Java?
- โจ๏ธ User Management Dashboard
- ๐ฅ SuperTokens vs Others
- ๐ ๏ธ Building from source
- ๐ฅ Community
- ๐ฉโ๐ป Contributing
- ๐ License
If you like our project, please :star2: this repository! For feedback, feel free to join our [Discord](https://supertokens.io/discord), or create an issue on this repo
๐ What is SuperTokens?
SuperTokens is an open-core alternative to proprietary login providers like Auth0 or AWS Cognito. We are
different because we offer:
- Open source: SuperTokens can be used for free, forever, with no limits on the number of users.
- An on-premises deployment so that you control 100% of your user data, using your own database.
- An end-to-end solution with login, sign-ups, user and session management, without all the complexities of OAuth
protocols. - Ease of implementation and higher security.
- Extensibility: Anyone can contribute and make SuperTokens better!
Philosophy
Authentication directly affects the UX, dev experience, and security of any app. We believe that
current solutions cannot optimize for all three "pillars", leading to many
applications hand-rolling their own auth. This not only leads to security issues but is also a massive
time drain.
We want to change that - we believe the only way is to provide a solution that has the right level of
abstraction gives you maximum control, is secure, and is simple to use - just like if you build it yourself,
from scratch (minus the time to learn, build, and maintain).
We also believe in the principle of least vendor lock-in. Your having full control of your user's data means that you
can switch away from SuperTokens without forcing your existing users to logout, reset their passwords, or in the worst
case, sign up again.
[Click here](https://thirdpartyemailpassword.demo.supertokens.com/) to see the demo app.
- Please visit our website to see the list of features.
- We want to make features as decoupled as possible. This means you can use SuperTokens for just login, or just session
management, or both. In fact, we also offer session management integrations with other login providers like Auth0.
Documentation
The docs can be seen on our website.
There is more information about SuperTokens on
the GitHub wiki section.
๐๏ธ Architecture
Please find an architecture diagram here
For more information, please visit
our GitHub wiki section.
โ Why Java?
- โ
Whilst running Java can seem difficult, we provide the JDK along with the binary/docker image when distributing it.
This makes running SuperTokens just like running any other HTTP microservice. - โ Java has a very mature ecosystem. This implies that third-party libraries have been battle-tested.
- โ
Java's strong type system ensures fewer bugs and easier maintainability. This is especially important when many
people are expected to work on the same project. - โ Our team is most comfortable with Java and hiring great Java developers is relatively easy as well.
- โ
One of the biggest criticisms of Java is memory usage. We have three solutions to this:
- The most frequent auth-related operation is session verification - this happens within the backend SDK (node,
python, Go) without contacting the Java core. Therefore, a single instance of the core can handle several 10s of
thousands of users fairly easily. - We have carefully chosen our dependencies. For eg: we use an embedded tomcat server instead of a higher-level web
framework. - We also plan on using GraalVM in the future and this can reduce memory usage by 95%!
- The most frequent auth-related operation is session verification - this happens within the backend SDK (node,
- โ
If you require any modifications to the auth APIs, those would need to be done on the backend SDK level (for example
Node, Golang, Python..). So youโd rarely need to directly modify/work with the Java code in this repo.
โจ๏ธ User Management Dashboard
Oversee your users with the SuperTokens User Management Dashboard
List users
List all the users who have signed up to your application.
Manage users
Manage users by modifying or deleting their sessions, metadata, roles and account info.
๐ฅ SuperTokens vs others
Please find a detailed comparison chart on our website
๐ ๏ธ Building from source
Please see our wiki for instructions.
๐ฅ Community
If you think this is a project you could use in the future, please :star2: this repository!
Contributors (across all SuperTokens repositories)
๐ฉโ๐ป Contributing
Please see the CONTRIBUTING.md file for
instructions.
๐ License
© 2020-2023 SuperTokens Inc and its contributors. All rights reserved.
Portions of this software are licensed as follows:
- All content that resides under the "ee/" directory of this repository, if that directory exists, is licensed under the
license defined in "ee/LICENSE.md". - All third-party components incorporated into the SuperTokens Software are licensed under the original license provided
by the owner of the applicable component. - Content outside of the above-mentioned directories or restrictions above is available under the "Apache 2.0"
license as defined in the level "LICENSE.md" file
2. Official Technical Reference & Guides (supertokens/docs)
File: README.md
SuperTokens Documentation
Welcome to the SuperTokens Documentation Repository!
This repository hosts the source files for the project's documentation. Our goal is to provide a comprehensive, accurate, and user-friendly documentation to help developers and users get the most out of our authentication service.
You can view the live documentation at https://supertokens.com/docs.
Contributing
SuperTokens provides an extensive authentication and authorization solution for various platforms and languages.
Hence, the product comes with a considerable layer of complexity that we try to clear up with this documentation.
If you think that we are missing any information or if you know how to make some aspects of the documentation better please create a PR.
We encourage you to contribute SuperToken's documentation! For a comprehensive guide on how to start the project and create your first commit please follow this document.
Reporting Issues
If you have found a problem with our documentation, please create an issue with a detailed description.
For quick fixes, like a misspelled word or a broken link, feel free to directly correct the document and merge your changes.
Go ahead and create a pull request with the solution.
Contact us
If you have other questions or if you want to discuss different aspects about our product, please join our Discord server or email us at [email protected].
File: docs/deployment/rate-limits.mdx
title: Rate limit policy hide_title: true sidebar_position: 3 Understand and implement SuperTokens rate limit policies for managed and self-hosted services. page_type: guide category: deployment
Rate limit policy
Overview
The following page describes how rate limits apply during SuperTokens API calls.
For managed service
The SuperTokens core of a managed account is rate limited on a per app and per IP address basis.
This means that if you query the core for app1 using the same IP address, the rate limit kicks in, and you get a 429 status code back from the core.
However, if you query the core using different IP addresses or for a different app, the rate limit of that does not interfere with the previous requests (that had another IP or was for another app).
Free tier
The free tier of the managed service has a rate limit of 50 requests per second with a burst of 50 requests per second (with no delay).
This should be enough for 5-10 concurrent sign in / up (each sign in API call queries the SuperTokens core multiple times).
:::important
The backend SDK auto retries if it gets a 429 status code from the core (up to 5 times before throwing an error).
:::
Paying users
If you are a paying user for SuperTokens, the rate limit and the burst limit adjust dynamically based on your usage (with a minimum of a 100rps).
You should not see any 429s unless there is a significant spike in requests.
:::info Paid Feature
If you want higher rate limits, please email support, requesting a higher rate limit.
:::
Special case
The /hello API exposed by the core is commonly used for health checks.
This API does not require any API key, and has its own rate limit of 5 requests per second per app (regardless of the IP address querying it).
This is independent to the rate limit described above, and cannot change.
For self hosted
The SuperTokens core has no rate limit other than for the /hello API (which is 5 requests per second per app).
You are free to add rate limits to the core by using a reverse proxy like Nginx.
If you want to implement rate limiting policy similar to the managed service described above, add the following to your http and server block in the nginx.conf file:
http {
# other configs..
map $request_uri $limit_req_zone_key {
"~^/(appId|appid)-(\w+)/?" $binary_remote_addr:$2;
default $binary_remote_addr;
}
limit_req_zone $limit_req_zone_key zone=mylimit:10m rate50/s;
limit_req_status 429;
# other configs..
upstream supertokens {
server localhost:3567;
}
server {
limit_req zone=mylimit burst=50 nodelay;
# other configs..
listen 0.0.0.0:80;
location / {
proxy_pass http://supertokens;
}
}
}In the above, the core adds a rate limit per app per IP address.
File: docs/deployment/scalability.mdx
id: scalability title: Scalability hide_title: true sidebar_position: 4 Learn how SuperTokens scales to handle millions of users and requests efficiently. page_type: guide category: deployment
Scalability
Overview
The following page addresses how the SuperTokens components scale based on different factors.
Users and tenants
SuperTokens can handle 10s of millions of users and tenants.
In fact, you can even make one tenant per user and it would work well.
For most operations, the database structure and queries allow partitioning based on tenants and users.
As the number of tenants scales, it does not affect performance on most operations per tenant. Similarly, as the number of users scales, it does not affect performance on most operations per user.
SuperTokens core
:::important
If you are using the managed service, the SuperTokens core is fully managed, and you don't have to worry about scaling it.
This section is for those who are self-hosting the SuperTokens core service.
:::
The SuperTokens core service supports horizontal scalability.
This means that you can add more instances of the core service to handle more requests.
The core service is also stateless, which means that you can add or remove instances without worrying about the state of the system.
The core service can handle a high number of requests per second (RPS).
The exact number of requests per second (RPS) that the core service can handle depends on the hardware you are using. In general, the core service can manage many requests.
For example, the average latency of requests is ~40 milliseconds at 100-150 requests per second (6,000-10,000 requests per minute).
The compute deployed is 6 instances of the SuperTokens core service, each on a t3.
micro EC2 instance behind a round robin load balancer. The CPU usage of each instance is around 10%.
The scale of end users that this can support is in the order of 1-2 million monthly active users, with a total user count of millions more.
Average latency over 1 day
Number of requests per minute over 1 day
Performance tuning
If you are facing performance issues, here are some tips to help you tune the performance of your SuperTokens setup:
- If you are self-hosting the SuperTokens core, know that it is stateless and can scale horizontally. You can add more instances of the core service to handle more requests (behind a load balancer).
- Check which part of the request cycle is slow. Is it the SuperTokens core responding, or is it the backend SDK APIs responding? The performance of the backend SDK API depends mainly on how you have set up your API layer (that integrates with the backend SDK) to perform. You can check which is slow by enabling debug logs in the backend SDK, and then inspecting the timestamps around the core requests. If they sum up to be much less than the total time taken for the request (from the
frontend's point of view), then the bottleneck is likely in the backend SDK. - If you are self-hosting the SuperTokens core, check if there are any database queries that are too slow. You can do this using debugging tools provided by the PostgreSQL database. If you find a query that's causing issues, please reach out to support.
- Check that the compute used to run the backend SDK, the SuperTokens core (in case you are self-hosting it), and the database is sufficient. Using a t3.micro EC2 instance for the core should work well for even 100,000 MAUs. You can check the
CPUand memory usage of the instances to see if they have maxed out, or if you have run out ofCPUcredits. If they are, you can consider upgrading the instances to more powerful ones. - In case you are self-hosting the SuperTokens core, you can tune its performance by setting different values for the following configurations in the configuration.yaml file, or docker
env:max_server_pool_size: Sets the max thread pool size for incominghttpserver requests. Default value is 10.postgresql_connection_pool_size(if using psql): Defines the connection pool size to PostgreSQL. Default value is 10.postgresql_minimum_idle_connections(if using psql): Minimum number of idle connections to remain active. If not set, minimum idle connections are the same as the connection pool size. By default, this is not set.postgresql_idle_connection_timeout: (if using psql): Timeout in milliseconds for the idle connections to close. Default is 60000 MS.
- Check if you have access token blacklisting enabled in the backend SDK. The default is
false, but if you have it enabled, then it means that every session verification attempt queries the SuperTokens core to check the database. This adds latency to the session verification process and increases the load on the core. If you want to keep this totrue, consider making it only for nonGETAPIs for your application. - You can increase the value of
access_token_validityin the SuperTokens core. It sets the validity of the access token. Default value is 3,600 seconds (1 hour). The lower this value, the more often the refresh API calls the core, increasing the load on the core.
Database
SuperTokens works with PostgreSQL databases, and one instance of the database is enough to handle tens of millions of MAUs.
For example, a database with 1 million users would occupy ~ 1.5 GB of disk space (assuming you add minimal custom metadata to the user object).
Backend SDK
The backend SDK does not store any information on its own.
It's a "big middleware" between the frontend requests and the SuperTokens core.
As such, its scalability depends entirely on the scalability of your API layer into which the backend SDK integrates.
Session verification
The access token is a JWT, and the backend SDK verifies them without any network requests, making them fast and scalable.
The core service verifies the refresh token, and the scalability of session refresh requests depends on the core service's scalability. However, session refreshes are rare compared to access token verification.
File: docs/deployment/self-host-supertokens.mdx
title: Self-host SuperTokens hide_title: true sidebar_position: 1 toc_max_heading_level: 4 Run SuperTokens on your infrastructure, control authentication data, and scale as needed. page_type: tutorial category: deployment
Self-hosting SuperTokens
Overview
One of the main features of SuperTokens is that you can run it using your own resources.
This way you have full control over the authentication data and you can scale based on your needs.
Before you start
To deploy the Core Service you must configure two things: the actual API and the database.
- The core service can be deployed using a Docker image or directly inside your VM.
- The supported database is PostgreSQL. The minimum required version is
13.0.
:::info
SuperTokens Core has dropped MySQL and MongoDB support with the 11.0.0 release.
If you want to reference the old documentation, please open this page.
:::
Steps
1. Install SuperTokens core
With docker
docker run -p 3567:3567 -d supertokens/supertokens-postgresql:latest- To see all the environment variables available, please see the README file.
- The above command starts the container with an in-memory database. This means you do not need to connect it to PostgreSQL to test out SuperTokens.
Without docker
1. Download SuperTokens
Once downloaded, remove the zip, and you see a folder named supertokens.
2. Install SuperTokens
# sudo is required so that the supertokens
# command can be added to your PATH variable.
cd supertokens
sudo ./install</OSTabs.TabItem>
<OSTabs.TabItem value="mac">
cd supertokens
./install:::caution
You may get an error like java cannot be opened because the developer cannot be verified. To solve this, visit System Preferences > Security & Privacy > General Tab, and then click on the Allow button at the bottom. Then retry the command above.
:::
</OSTabs.TabItem>
<OSTabs.TabItem value="windows">
Rem run as an Administrator. This is required so that the supertokens
Rem command can be added to your PATH.
cd supertokens
install.bat</OSTabs.TabItem>
:::important
After installing, you can delete the downloaded folder as you no longer need it.
Make any changes to the configuration in the config.yaml file in the installation directory, as specified in the output of the supertokens --help command.
:::
3. Start the core service
Running the following command starts the service.
supertokens start [--host=...] [--port=...]- The above command starts the container with an in-memory database.
- To see all available options please run
supertokens start --help
:::info Tip
To stop the service, run the following command:
supertokens stop:::
2. Test that the service is running
Open a browser and visit http://localhost:3567/hello. If you see a page that says Hello back, then the container started successfully!
If you are having issues with starting the docker image, please feel free to reach out over email or via Discord.
:::tip
The /hello route checks whether the database connection is correctly set up and only returns a 200 status code if there is no issue.
If you are using Kubernetes or docker swarm, this endpoint is perfect for doing readiness and liveness probes.
:::
3. Connect the backend SDK with SuperTokens
- The default
portfor SuperTokens is3567. You can change this by binding a different port in thedocker runcommand. For example,docker run -p 8080:3567runs SuperTokens on port8080on your machine. - The connection info goes in the
supertokensobject in theinitfunction on your backend:
import supertokens from "supertokens-node";
supertokens.init({
// highlight-start
supertokens: {
connectionURI: "http://localhost:3567",
apiKey: "someKey" // OR can be undefined
},
// highlight-end
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: []
});import "github.com/supertokens/supertokens-golang/supertokens"
func main() {
supertokens.Init(supertokens.TypeInput{
// highlight-start
Supertokens: &supertokens.ConnectionInfo{
ConnectionURI: "http://localhost:3567",
APIKey: "someKey",
},
// highlight-end
})
}from supertokens_python import init, InputAppInfo, SupertokensConfig
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
# highlight-start
supertokens_config=SupertokensConfig(
connection_uri='http://localhost:3567',
api_key='someKey'
),
# highlight-end
framework='...', # type: ignore
recipe_list=[
#...
]
):::info Security
No API key exists by default.
Check the API keys documentation to see how to add one.
:::
4. Set up the database
4.1 Create a database {{optional}}
CREATE DATABASE supertokens;You can skip this step if you want SuperTokens to write to your own database.
In this case, you need to provide your database's name as shown in the step below.
4.2 Connect SuperTokens to your database
With docker
:::caution
Host being localhost / 127.0.0.1 does not work in a docker image. Instead, please provide the database's local / public host name or IP address.
You also need to make the database listen on all the IPs of the local machine.
Edit the postgresql.conf configuration file and set the value of listen_addresses to 0.0.0.0.
:::
:::caution
It is important to use the postgresql:// scheme designator in the PostgreSQL Connection URI. Using postgres:// will lead to a startup error.
:::
docker run \
-p 3567:3567 \
// highlight-next-line
-e POSTGRESQL_CONNECTION_URI="postgresql://username:pass@host/dbName" \
-d supertokens/supertokens-postgresql
# OR
docker run \
-p 3567:3567 \
// highlight-start
-e POSTGRESQL_USER="username" \
-e POSTGRESQL_PASSWORD="password" \
-e POSTGRESQL_HOST="host" \
-e POSTGRESQL_PORT="5432" \
-e POSTGRESQL_DATABASE_NAME="supertokens" \
// highlight-end
-d supertokens/supertokens-postgresql:::tip
You can also provide the table schema by setting the POSTGRESQL_TABLE_SCHEMA option.
:::
Without docker
# You need to add the following to the config.yaml file.
# The file path can be found by running the "supertokens --help" command
postgresql_connection_uri: "postgresql://username:pass@host/dbName"
# OR
postgresql_user: "username"
postgresql_password: "password"
postgresql_host: "host"
postgresql_port: "5432"
postgresql_database_name: "supertokens"You can also provide the table schema by setting the postgresql_table_schema option.
:::info
The required tables should create automatically if the database user has table creation permission.
If not, you can create them manually using the following snippet.
/* Detailed source-code truncated for AI context efficiency. */:::
4.3 Test the connection
To test, start SuperTokens and run the following query in your database
SELECT * FROM key_value;If you see at least one row, it means that the connection has been successfully completed!
4.4 Rename database tables {{optional}}
:::caution
If you already have tables created by SuperTokens, and then you rename them, SuperTokens creates new tables. Please be sure to migrate the data from the existing one to the new one.
:::
You can add a prefix to all table names that SuperTokens manages. This way, all will be renamed in a way that has no clashes with your tables.
For example, two tables created by SuperTokens have the names emailpassword_users and thirdparty_users. If you add a prefix to them (something like "my_prefix"), then the tables become my_prefix_emailpassword_users and my_prefix_thirdparty_users.
docker run \
-p 3567:3567 \
// highlight-next-line
-e POSTGRESQL_TABLE_NAMES_PREFIX="my_prefix" \
-d supertokens/supertokens-postgresql</SelfHostingTabs.TabItem>
<SelfHostingTabs.TabItem value="without-docker">
# You need to add the following to the config.yaml file.
# The file path can be found by running the "supertokens --help" command
postgresql_table_names_prefix: "my_prefix"</SelfHostingTabs.TabItem>
5. Add license keys
To access some features in your self-hosted service you must use license keys.
You can sign up on SuperTokens to receive one.
Once you have the license key you need to manually add it to your SuperTokens Core Instance.
To do this you have to call the Core API with the following request:
curl --location --request PUT ^{coreInfo.uri}/ee/license \
--header 'Content-Type: application/json' \
--header 'api-key: ^{coreInfo.key}' \
--data-raw '{ "licenseKey": "<YOUR_LICENSE_KEY>" }'References
Docker compose file
version: '3'
services:
# Note: If you are assigning a custom name to your db service on the line below, make sure it does not contain underscores
db:
image: 'postgres:latest'
environment:
POSTGRES_USER: supertokens_user
POSTGRES_PASSWORD: somePassword
POSTGRES_DB: supertokens
ports:
- 5432:5432
networks:
- app_network
restart: unless-stopped
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'supertokens_user', '-d', 'supertokens']
interval: 5s
timeout: 5s
retries: 5
supertokens:
image: supertokens/supertokens-postgresql:latest
depends_on:
db:
condition: service_healthy
ports:
- 3567:3567
environment:
POSTGRESQL_CONNECTION_URI: "postgresql://supertokens_user:somePassword@db:5432/supertokens"
networks:
- app_network
restart: unless-stopped
healthcheck:
test: >
bash -c 'exec 3<>/dev/tcp/127.0.0.1/3567 && echo -e "GET /hello HTTP/1.1\r\nhost: 127.0.0.1:3567\r\nConnection: close\r\n\r\n" >&3 && cat <&3 | grep "Hello"'
interval: 10s
timeout: 5s
retries: 5
networks:
app_network:
driver: bridge:::important
If you are running the backend process that integrates with the backend SDK as part of the docker compose file as well, make sure to use http://supertokens:3567 as the connection URI instead of http://localhost:3567.
:::
Helm charts for Kubernetes
- For PostgreSQL image
File: docs/authentication/email-password/customize-the-sign-in-form.mdx
title: Customize the Sign In form hide_title: true sidebar_position: 2 Customize form fields in the sign in component by modifying labels, placeholders, default values, and error messages. page_type: guide recipe: emailpassword category: email-password
Customize form fields
Before you start
The following instructions are only relevant if you are using the pre-built UI components.
If you have created your own authentication UI, you can skip this guide.
Modify labels and placeholders
To change the labels and placeholders of the fields update the formFields property, in the recipe configuration.
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signInForm: {
// highlight-start
formFields: [{
id: "email",
label: "customFieldName",
placeholder: "Custom value"
}]
// highlight-end
}
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signInForm: {
// highlight-start
formFields: [{
id: "email",
label: "customFieldName",
placeholder: "Custom value"
}]
// highlight-end
}
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Set default values
Add a getDefaultValue option in the formFields configuration to pre-fill the inputs.
Keep in mind that the function needs to return a string.
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signInForm: {
formFields: [{
id: "email",
label: "Your Email",
// highlight-start
getDefaultValue: () => "[email protected]"
// highlight-end
}]
}
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signInForm: {
formFields: [{
id: "email",
label: "Your Email",
// highlight-start
getDefaultValue: () => "[email protected]"
// highlight-end
}]
}
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Change the optional error message
When you try to submit the login form without filling in the required fields, the UI, by default, shows an error stating that the Field is not optional.
To customize this message set the nonOptionalErrorMsg property to a custom string.
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signInForm: {
formFields: [{
id: "email",
label: "Your Email",
placeholder: "Email",
// highlight-start
nonOptionalErrorMsg: "Please add your email"
// highlight-end
}]
}
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signInForm: {
formFields: [{
id: "email",
label: "Your Email",
placeholder: "Email",
// highlight-start
nonOptionalErrorMsg: "Please add your email"
// highlight-end
}]
}
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Custom field validators
To add custom validation logic to the sign in form, update the sign up form configuration.
The email and password fields validation synchronizes between the two forms.
Check the sign up form instructions for more details.
See also
<ReferenceCard.Grid>
</ReferenceCard.Grid>
File: docs/authentication/email-password/customize-the-sign-up-form.mdx
title: Customize the Sign Up Form hide_title: true sidebar_position: 3 toc_max_heading_level: 4 page_type: guide recipe: emailpassword category: email-password
Customize the sign up form
Before you start
The next instructions assume that you have a working application that uses SuperTokens for authentication.
If not, please refer to the quickstart guide and then return here.
Add extra fields
To include more fields in the sign up form, you need to first update both the frontend and backend configuration.
Then, when the sing up payload arrives on the backend, you should add a way to persist those values.
1. Add the new fields to the UI
You first need to add the new fields to your sign up interface.
Given that you are using a custom implementation, the steps vary based on your code.
After you have updated the form, ensure that the submit action follows the next example.
import { signUp } from "supertokens-web-js/recipe/emailpassword";
async function signUpClicked(email: string, password: string, name: string, age: number, country: string) {
let response = await signUp({
formFields: [{
id: "email",
value: email
}, {
id: "password",
value: password
},{
id: "name",
value: name
}, {
id: "age",
value: age + ""
}, {
id: "country",
value: country
}]
})
// ... rest of the code
}</NpmOrScriptsCard.Content>
<NpmOrScriptsCard.Content value="scripts">
import supertokensEmailPassword from "supertokens-web-js-script/recipe/emailpassword";
async function signUpClicked(email: string, password: string, name: string, age: number, country: string) {
let response = await supertokensEmailPassword.signUp({
formFields: [{
id: "email",
value: email
}, {
id: "password",
value: password
}, {
id: "name",
value: name
}, {
id: "age",
value: age + ""
}, {
id: "country",
value: country
}]
})
// ... rest of the code
}</NpmOrScriptsCard.Content>
</FrontendCustomUITabs.TabItem>
<FrontendCustomUITabs.TabItem value="mobile">
curl --location --request POST '^{appInfo.apiDomain}^{appInfo.apiBasePath}/signup'
--header 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"formFields": [{
"id": "email",
"value": "[email protected]"
}, {
"id": "password",
"value": "somePassword123"
}, {
id: "name",
value: "John Doe"
}, {
id: "age",
value: 27
}, {
id: "country",
value: "USA"
}]
}'</FrontendCustomUITabs.TabItem>
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
// highlight-start
signUpForm: {
formFields: [{
id: "name",
label: "Full name",
placeholder: "First name and last name"
}, {
id: "age",
label: "Your age",
placeholder: "How old are you?",
}, {
id: "country",
label: "Your country",
placeholder: "Where do you live?",
optional: true
}]
}
// highlight-end
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
// highlight-start
signUpForm: {
formFields: [{
id: "name",
label: "Full name",
placeholder: "First name and last name"
}, {
id: "age",
label: "Your age",
placeholder: "How old are you?",
}, {
id: "country",
label: "Your country",
placeholder: "Where do you live?",
optional: true
}]
}
// highlight-end
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Create custom components
By default, the new fields use input elements.
To enable more complex fields you can create your own custom components.
:::important
You may need to disable the Shadow DOM if you're integrating with a different component library that requires you to import its own CSS. For instance, some component libraries, such as react-international-phone, might expect you to include their CSS alongside their components. For more information, refer to Disable use of shadow DOM.
:::
Set the inputComponent property for each field that you want to customize.
/* Detailed source-code truncated for AI context efficiency. */</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
:::caution
This is not applicable for non React apps. You have to create your own custom UI instead.
:::
</FrontendPrebuiltUITabs.TabItem>
2. Include the extra fields in the backend configuration
Change the Backend SDK initialization call to ensure that the system processes the new fields when a new user registers.
import SuperTokens from "supertokens-node";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
signUpFeature: {
// highlight-start
formFields: [{
id: "name"
}, {
id: "age"
}, {
id: "country",
optional: true
}]
// highlight-end
}
}),
Session.init({ /* ... */ })
]
});import (
"github.com/supertokens/supertokens-golang/recipe/emailpassword"
"github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
countryOptional := true
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
emailpassword.Init(&epmodels.TypeInput{
SignUpFeature: &epmodels.TypeInputSignUp{
//highlight-start
FormFields: []epmodels.TypeInputFormField{
{
ID: "name",
},
{
ID: "age",
},
{
ID: "country",
Optional: &countryOptional,
},
},
//highlight-end
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import emailpassword, session
from supertokens_python.recipe.emailpassword import InputFormField
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...', # type: ignore
recipe_list=[
emailpassword.init(
# highlight-start
sign_up_feature=emailpassword.InputSignUpFeature(
form_fields=[InputFormField(id='name'), InputFormField(id='age'), InputFormField(id='country', optional=True)]
)
# highlight-end
),
session.init()
]
)3. Save the values after a successful sign up
Use the signUpPOST API function to process the field values and persist them.
:::caution
SuperTokens does not store custom form fields. You can either save them in your database or use the User Metadata feature .
:::
import SuperTokens from "supertokens-node";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
override: {
apis: (originalImplementation) => {
return {
...originalImplementation,
signUpPOST: async function (input) {
if (originalImplementation.signUpPOST === undefined) {
throw Error("Should never come here");
}
// First we call the original implementation of signUpPOST.
let response = await originalImplementation.signUpPOST(input);
// Post sign up response, we check if it was successful
if (response.status === "OK") {
// These are the input form fields values that the user used while signing up
let formFields = input.formFields;
}
return response;
}
}
}
}
}),
Session.init({ /* ... */ })
]
});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 {
// First we copy the original implementation func
originalSignUpPOST := *originalImplementation.SignUpPOST
(*originalImplementation.SignUpPOST) = func(formFields []epmodels.TypeFormField, tenantId string, options epmodels.APIOptions, userContext supertokens.UserContext) (epmodels.SignUpPOSTResponse, error) {
resp, err := originalSignUpPOST(formFields, tenantId, options, userContext)
if err != nil {
return epmodels.SignUpPOSTResponse{}, err
}
if resp.OK != nil {
// sign up was successful
// TODO: You can now read the formFields from the input params
}
return resp, err
}
return originalImplementation
},
},
}),
},
})
}/* Detailed source-code truncated for AI context efficiency. */Customize each form field
:::caution Not applicable
This section is not relevant for custom UI, as you create your own UI and already have control over the form fields.
:::
Modify labels and placeholders
To change the labels and placeholders of the fields, update the formFields property, in the recipe configuration.
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword"
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signUpForm: {
// highlight-start
formFields: [{
id: "email",
label: "customFieldName",
placeholder: "Custom value"
}]
}
// highlight-end
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signUpForm: {
// highlight-start
formFields: [{
id: "email",
label: "customFieldName",
placeholder: "Custom value"
}]
}
// highlight-end
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Set default values
Add a getDefaultValue option to the formFields configuration to set default values.
Keep in mind that the function needs to return a string.
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword"
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
id: "email",
label: "Your Email",
// highlight-start
getDefaultValue: () => "[email protected]"
// highlight-end
}, {
id: "name",
label: "Full name",
// highlight-start
getDefaultValue: () => "John Doe",
// highlight-end
}]
}
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
id: "email",
label: "Your Email",
// highlight-start
getDefaultValue: () => "[email protected]"
// highlight-end
}, {
id: "name",
label: "Full name",
// highlight-start
getDefaultValue: () => "John Doe",
// highlight-end
}]
}
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Change the optional error message
When you try to submit the login form without filling in the required fields, the UI, by default, shows an error stating that the Field is not optional.
To customize this message set the nonOptionalErrorMsg property to a custom string.
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword"
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
id: "email",
label: "Your Email",
placeholder: "Email",
// highlight-start
nonOptionalErrorMsg: "Please add your email"
// highlight-end
}, {
id: "name",
label: "Full name",
placeholder: "Name",
// highlight-start
nonOptionalErrorMsg: "Full name is required",
// highlight-end
}]
}
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword"
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
id: "email",
label: "Your Email",
placeholder: "Email",
// highlight-start
nonOptionalErrorMsg: "Please add your email"
// highlight-end
}, {
id: "name",
label: "Full name",
placeholder: "Name",
// highlight-start
nonOptionalErrorMsg: "Full name is required",
// highlight-end
}]
}
}
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
Change the field order
To customize the order of fields in your sign up form, override the EmailPasswordSignUpForm component.
Use the next example as a reference.
import React from "react";
import { SuperTokensWrapper } from "supertokens-auth-react";
import { EmailPasswordComponentsOverrideProvider } from "supertokens-auth-react/recipe/emailpassword";
function App() {
return (
<SuperTokensWrapper>
{
return (
<DefaultComponent
{...props}
formFields={[
props.formFields.find(({ id }) => id === 'name')!,
props.formFields.find(({ id }) => id === 'email')!,
props.formFields.find(({ id }) => id === 'password')!,
]}
/>
);
},
// highlight-end
}}>
{/* Rest of the JSX */}
</SuperTokensWrapper>
);
}
export default App;import React from "react";
import { SuperTokensWrapper } from "supertokens-auth-react";
import { EmailPasswordPreBuiltUI } from 'supertokens-auth-react/recipe/emailpassword/prebuiltui';
import { EmailPasswordComponentsOverrideProvider } from "supertokens-auth-react/recipe/emailpassword";
import { getRoutingComponent, canHandleRoute } from "supertokens-auth-react/ui"
function App() {
if (canHandleRoute([EmailPasswordPreBuiltUI])) {
return (
{
return (
<DefaultComponent
{...props}
formFields={[
props.formFields.find(({ id }) => id === 'name')!,
props.formFields.find(({ id }) => id === 'email')!,
props.formFields.find(({ id }) => id === 'password')!,
]}
/>
);
},
// highlight-end
}}>
{getRoutingComponent([EmailPasswordPreBuiltUI])}
)
}
return (
<SuperTokensWrapper>
{/* Rest of the JSX */}
</SuperTokensWrapper>
);
}
export default App;</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
:::caution
This is not applicable for non React apps. You have to create your own custom UI instead.
:::
</FrontendPrebuiltUITabs.TabItem>
Change field validators
1. Update the frontend configuration
:::caution Not applicable
For your custom UI, you have to implement field validation checking yourself.
Note that you need to also update the backend validation to ensure a complete flow.
Check the next section for more details.
:::
Add a validate method to any of your formFields.
The following example shows how to add age verification to the form:
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
id: "name",
label: "Full name",
placeholder: "First name and last name"
}, {
id: "age",
label: "Your age",
placeholder: "How old are you?",
optional: true,
/* Validation method to make sure that age is above 18 */
// highlight-start
validate: async (value) => {
if (parseInt(value) > 18) {
return undefined; // means that there is no error
}
return "You must be over 18 to register";
}
// highlight-end
}, {
id: "country",
label: "Your country",
placeholder: "Where do you live?",
optional: true
}]
}
}
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
/* Detailed source-code truncated for AI context efficiency. */</FrontendPrebuiltUITabs.TabItem>
2. Update the backend configuration
Add validate functions to each of the form fields, in the backend SDK initialization call.
import SuperTokens from "supertokens-node";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
signUpFeature: {
formFields: [{
id: "name"
}, {
id: "age",
/* Validation method to make sure that age >= 18 */
// highlight-start
validate: async (value, tenantId) => {
if (parseInt(value) >= 18) {
return undefined; // means that there is no error
}
return "You must be over 18 to register";
}
// highlight-end
}, {
id: "country",
optional: true
}]
}
}),
Session.init({
})
]
});import (
"strconv"
"github.com/supertokens/supertokens-golang/recipe/emailpassword"
"github.com/supertokens/supertokens-golang/recipe/emailpassword/epmodels"
"github.com/supertokens/supertokens-golang/supertokens"
)
func main() {
countryOptional := true
supertokens.Init(supertokens.TypeInput{
RecipeList: []supertokens.Recipe{
emailpassword.Init(&epmodels.TypeInput{
SignUpFeature: &epmodels.TypeInputSignUp{
FormFields: []epmodels.TypeInputFormField{
{
ID: "name",
},
{
ID: "age",
//highlight-start
Validate: func(value interface{}, tenantId string) *string {
age, _ := strconv.Atoi(value.(string))
if age >= 18 {
// return nil to indicate success
return nil
}
err := "You must be over 18 to register"
return &err
},
//highlight-end
},
{
ID: "country",
Optional: &countryOptional,
},
},
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import emailpassword
from supertokens_python.recipe.emailpassword import InputFormField
from typing import Any
# highlight-start
async def validate_age(value: Any, tenant_id: str):
# Validation method to make sure that age >= 18
if int(value) >= 18:
return None # means that there is no error
return 'You must be over 18 to register'
# highlight-end
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...', # type: ignore
recipe_list=[
emailpassword.init(
sign_up_feature=emailpassword.InputSignUpFeature(
form_fields=[
InputFormField(id='name'),
# highlight-start
InputFormField(id='age', validate=validate_age),
# highlight-end
InputFormField(id='country', optional=True)
]
)
)
]
):::info Multi-tenancy
Notice the tenantId argument passed into the validate function. Using that, you can define custom logic per tenant. For example, you can define different password policies for different tenants.
:::
Change the default email and password validators
By default, SuperTokens adds an email and a password validator to the sign up form.
- The default email validator makes sure that the provided email is in the correct email format.
- The default password validator makes sure that the provided password:
- has a minimum of 8 characters.
- contains at least one lowercase character
- contains at least one number
To add your own validators follow the steps described initially in this section.
:::important
- The email validator that you define for sign up is also applied automatically to sign in.
- The password validator that you define for sign up is also applied automatically to reset password forms.
:::
Here is an example of what you need to change.
1. Update the frontend configuration
:::caution Not applicable
For your custom UI, you have to implement field validation checking yourself.
Note that you need to also update the backend validation to ensure a complete flow.
Check the next section for more details.
:::
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
// highlight-start
id: "email",
label: "...",
validate: async (value) => {
// Your own validation returning a string or undefined if no errors.
return "...";
}
}, {
id: "password",
label: "...",
validate: async (value) => {
// Your own validation returning a string or undefined if no errors.
return "...";
}
// highlight-end
}]
}
}
})
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [{
// highlight-start
id: "email",
label: "...",
validate: async (value) => {
// Your own validation returning a string or undefined if no errors.
return "...";
}
}, {
id: "password",
label: "...",
validate: async (value) => {
// Your own validation returning a string or undefined if no errors.
return "...";
}
// highlight-end
}]
}
}
})
]
});</FrontendPrebuiltUITabs.TabItem>
1. Update the backend configuration
import SuperTokens from "supertokens-node";
import EmailPassword from "supertokens-node/recipe/emailpassword";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
signUpFeature: {
formFields: [{
// highlight-start
id: "email",
validate: async (value, tenantId) => {
// Your own validation returning a string or undefined if no errors.
return "...";
}
}, {
id: "password",
validate: async (value, tenantId) => {
// Your own validation returning a string or undefined if no errors.
return "...";
}
// highlight-end
}]
}
}),
]
});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{
SignUpFeature: &epmodels.TypeInputSignUp{
FormFields: []epmodels.TypeInputFormField{
//highlight-start
{
ID: "email",
Validate: func(value interface{}, tenantId string) *string {
// Your own validation returning a string or nil if no errors.
return nil
},
},
{
ID: "password",
Validate: func(value interface{}, tenantId string) *string {
// Your own validation returning a string or nil if no errors.
return nil
},
},
//highlight-end
},
},
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import emailpassword
from supertokens_python.recipe.emailpassword import InputFormField
from typing import Any
# highlight-start
async def validate_password(value: Any, tenant_id: str):
pass # TODO
# highlight-end
# highlight-start
async def validate_email(value: Any, tenant_id: str):
pass # TODO
# highlight-end
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...', # type: ignore
recipe_list=[
emailpassword.init(
sign_up_feature=emailpassword.InputSignUpFeature(
form_fields=[
# highlight-start
InputFormField(id='password', validate=validate_password),
InputFormField(id='email', validate=validate_email)
# highlight-end
]
)
)
]
)Add terms of service and privacy policy links
To add "Terms of service" and "Privacy policy" links to your sign up page add the links in the frontend SDK initialization call.
Based on the provided configuration the data renders in the following way:
- Provided both links: "By signing up, you agree to the Terms of Service and Privacy Policy"
- Provided only Terms of service link: "By signing up, you agree to the Terms of Service"
- Provided only Privacy policy link: "By signing up, you agree to the Privacy Policy"
import SuperTokens from "supertokens-auth-react";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
// highlight-start
termsOfServiceLink: "https://example.com/terms-of-service",
privacyPolicyLink: "https://example.com/privacy-policy",
// highlight-end
recipeList: [/* ... */]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
// highlight-start
termsOfServiceLink: "https://example.com/terms-of-service",
privacyPolicyLink: "https://example.com/privacy-policy",
// highlight-end
recipeList: [/* ... */]
});</FrontendPrebuiltUITabs.TabItem>
:::caution
Not applicable since you do not use the pre-built UI.
:::
See also
<ReferenceCard.Grid>
</ReferenceCard.Grid>
File: docs/authentication/email-password/disable-signup.mdx
title: Disable Sign Up hide_title: true sidebar_position: 8 page_type: guide recipe: emailpassword category: email-password
Disable the Sign Up Flow
Overview
In order to prevent users from signing up directly through the frontend, you can disable the sign up flow.
This can be done in two steps:
- Update the UI to get rid of any sign up information
- Change the Backend SDK to prevent sign up attempts
Before you start
This guide assumes that you already have configured your application to use SuperTokens for authentication.
If you have not, please check the Quickstart Guide.
Remove the sign up UI
<FrontendPrebuiltUITabs exclude={["mobile"]}>
<FrontendPrebuiltUITabs.TabItem value="reactjs">
Remove the sign up UI by overriding the AuthPageComponentList component and setting the showSuperTokensAuth prop to false.
import React from "react";
import { SuperTokensWrapper } from "supertokens-auth-react";
import { AuthRecipeComponentsOverrideContextProvider } from "supertokens-auth-react/ui";
import { EmailPasswordComponentsOverrideProvider } from "supertokens-auth-react/recipe/emailpassword"
import { ThirdpartyComponentsOverrideProvider } from "supertokens-auth-react/recipe/thirdparty";
function App() {
return (
<SuperTokensWrapper>
<AuthRecipeComponentsOverrideContextProvider
components={{
AuthPageComponentList_Override: ({ DefaultComponent, ...props }) => {
return <DefaultComponent {...props} hasSeparateSignUpView={false} />;
},
}}>
</AuthRecipeComponentsOverrideContextProvider>
</SuperTokensWrapper>
);
}
export default App;</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
Remove the sign up UI by customizing the CSS of the authentication page.
// this goes in the auth route config of your frontend app (once the pre built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
style: `
[data-supertokens~=authPage] [data-supertokens~=headerSubtitle] {
display: none;
}
`,
recipeList: [ /* ... */]
});</FrontendPrebuiltUITabs.TabItem>
If you have a custom UI, this step will depend on your implementation.
Just make sure that the user will not be able to view any sign up elements on the authentication page.
Disable the Backend SDK sign up endpoints
Override the Backend SDK API functions to prevent sign up attempts.
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,
// highlight-next-line
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 {
// highlight-next-line
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):
# highlight-next-line
original_impl.disable_sign_up_post = True
return original_impl
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework='...', # type: ignore
recipe_list=[
emailpassword.init(
override=emailpassword.InputOverrideConfig(
apis=apis_override
),
)
]
)See also
<ReferenceCard.Grid>
<ReferenceCard.Title>Account Creation</ReferenceCard.Title>
<ReferenceCard.Description>Import accounts using the SuperTokens API.</ReferenceCard.Description>
<ReferenceCard.Title>User Management</ReferenceCard.Title>
<ReferenceCard.Description>SDK functions that can be used to manage users.</ReferenceCard.Description>
<ReferenceCard.Title>Dashboard</ReferenceCard.Title>
<ReferenceCard.Description>UI exposed by the SuperTokens SDK that allows you to view and manage users.</ReferenceCard.Description>
</ReferenceCard.Grid>
File: docs/authentication/email-password/hooks-and-overrides.mdx
title: Hooks and overrides hide_title: true sidebar_position: 4 Add custom logic in the authentication flow by overriding the SuperTokens APIs. page_type: guide recipe: emailpassword category: email-password
Hooks and overrides
SuperTokens exposes a set of constructs that allow you to trigger different actions during the authentication lifecycle or to even fully customize the logic based on your use case.
The following sections describe how you can modify adjust the emailpassword recipe to your needs.
Explore the references pages for a more in depth guide on hooks and overrides.
Sign in
Frontend hook
This method gets fired, with the SUCCESS action, immediately after a successful sign in or sign up.
Follow the code snippet to determine if the user is signing up or signing in.
With this method you can fire events immediately after a successful sign in.
You can use it to send analytics events.
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
// highlight-start
onHandleEvent: async (context) => {
if (context.action === "SUCCESS") {
if (context.isNewRecipeUser && context.user.loginMethods.length === 1) {
// TODO: Sign up
} else {
// TODO: Sign in
}
}
}
// highlight-end
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
// highlight-start
onHandleEvent: async (context) => {
if (context.action === "SUCCESS") {
if (context.isNewRecipeUser && context.user.loginMethods.length === 1) {
// TODO: Sign up
} else {
// TODO: Sign in
}
}
}
// highlight-end
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
:::caution Not applicable
This section is not applicable for custom UI since you are calling the sign in API yourself anyway. You can perform anything you want to do post sign in based on the result of the API call.
:::
Backend override
Overriding the signIn function allows you to introduce your own logic for the sign in process.
Use it to persist different types of data or trigger actions.
/* Detailed source-code truncated for AI context efficiency. */import (
"fmt"
"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{
//highlight-start
Override: &epmodels.OverrideStruct{
Functions: func(originalImplementation epmodels.RecipeInterface) epmodels.RecipeInterface {
// create a copy of the originalImplementation func
originalSignIn := *originalImplementation.SignIn
// override the sign in up function
(*originalImplementation.SignIn) = func(email, password, tenantId string, userContext supertokens.UserContext) (epmodels.SignInResponse, error) {
// First we call the original implementation of SignIn.
response, err := originalSignIn(email, password, tenantId, userContext)
if err != nil {
return epmodels.SignInResponse{}, err
}
if response.OK != nil {
// sign in was successful
// user object contains the ID and email
user := response.OK.User
// TODO: Post sign in logic.
fmt.Println(user)
}
return response, nil
}
return originalImplementation
},
},
//highlight-end
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session, emailpassword
from supertokens_python.recipe.emailpassword.interfaces import (
RecipeInterface,
SignInOkResult,
)
from typing import Dict, Any, Union
from supertokens_python.recipe.session.interfaces import SessionContainer
# highlight-start
def override_emailpassword_functions(
original_implementation: RecipeInterface,
) -> RecipeInterface:
original_sign_in = original_implementation.sign_in
async def 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],
):
result = await original_sign_in(
email,
password,
tenant_id,
session,
should_try_linking_with_session_user,
user_context,
)
if isinstance(result, SignInOkResult):
id = result.user.id
emails = result.user.emails
print(id)
print(emails)
# TODO: post sign in logic
return result
original_implementation.sign_in = sign_in
return original_implementation
# highlight-end
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework="...", # type: ignore
recipe_list=[
emailpassword.init(
# highlight-start
override=emailpassword.InputOverrideConfig(
functions=override_emailpassword_functions
),
# highlight-end
),
session.init(),
],
)Sign up
Frontend hook
This method gets fired, with the SUCCESS action, immediately after a successful sign in or sign up.
Follow the code snippet to determine if the user is signing up.
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
// highlight-start
onHandleEvent: async (context) => {
if (context.action === "SUCCESS") {
if (context.isNewRecipeUser && context.user.loginMethods.length === 1) {
// TODO: Sign up
} else {
// TODO: Sign in
}
}
}
// highlight-end
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
// highlight-start
onHandleEvent: async (context) => {
if (context.action === "SUCCESS") {
if (context.isNewRecipeUser && context.user.loginMethods.length === 1) {
// TODO: Sign up
} else {
// TODO: Sign in
}
}
}
// highlight-end
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
:::caution Not applicable
This section is not applicable for custom UI since you are calling the sign up API yourself anyway. You can perform anything you want to do post sign up based on the result of the API call.
:::
Backend override
Overriding the signUp function allows you to introduce your own logic for the sign in process.
Use it to persist different types of data, synchronize users between SuperTokens and your systems or to trigger other types of actions.
/* Detailed source-code truncated for AI context efficiency. */import (
"fmt"
"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{
//highlight-start
Override: &epmodels.OverrideStruct{
Functions: func(originalImplementation epmodels.RecipeInterface) epmodels.RecipeInterface {
// create a copy of the originalImplementation func
originalSignUp := *originalImplementation.SignUp
// override the sign in up function
(*originalImplementation.SignUp) = func(email, password, tenantId string, userContext supertokens.UserContext) (epmodels.SignUpResponse, error) {
// First we call the original implementation of SignUp.
response, err := originalSignUp(email, password, tenantId, userContext)
if err != nil {
return epmodels.SignUpResponse{}, err
}
if response.OK != nil {
// sign up was successful
// user object contains the ID and email
user := response.OK.User
// TODO: Post sign up logic.
fmt.Println(user)
}
return response, nil
}
return originalImplementation
},
},
//highlight-end
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import session, emailpassword
from supertokens_python.recipe.emailpassword.interfaces import (
RecipeInterface,
SignUpOkResult,
)
from typing import Dict, Any, Union
from supertokens_python.recipe.session.interfaces import SessionContainer
# highlight-start
def override_emailpassword_functions(
original_implementation: RecipeInterface,
) -> RecipeInterface:
original_sign_up = original_implementation.sign_up
async def sign_up(
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],
):
result = await original_sign_up(
email,
password,
tenant_id,
session,
should_try_linking_with_session_user,
user_context,
)
if isinstance(result, SignUpOkResult) and len(result.user.login_methods) == 1:
id = result.user.id
emails = result.user.emails
print(id)
print(emails)
# TODO: post sign up logic
return result
original_implementation.sign_up = sign_up
return original_implementation
# highlight-end
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework="...", # type: ignore
recipe_list=[
emailpassword.init(
# highlight-start
override=emailpassword.InputOverrideConfig(
functions=override_emailpassword_functions
),
# highlight-end
),
session.init(),
],
)Password reset
Frontend hook
This method gets fired during the password reset flow with either the PASSWORD_RESET_SUCCESSFUL or RESET_PASSWORD_EMAIL_SENT action.
Use it to fire analytics events or to add any additional logic.
<FrontendPrebuiltUITabs.TabItem value="reactjs">
import SuperTokens from "supertokens-auth-react";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
// highlight-start
onHandleEvent: async (context) => {
if (context.action === "PASSWORD_RESET_SUCCESSFUL") {
// Add you custom logic here
} else if (context.action === "RESET_PASSWORD_EMAIL_SENT") {
// Add you custom logic here
}
}
// highlight-end
}),
Session.init()
]
});</FrontendPrebuiltUITabs.TabItem>
<FrontendPrebuiltUITabs.TabItem value="angular">
// this goes in the auth route config of your frontend app (once the pre-built UI script has been loaded)
import {init as supertokensUIInit} from "supertokens-auth-react-script";
import supertokensUIEmailPassword from "supertokens-auth-react-script/recipe/emailpassword";
import supertokensUISession from "supertokens-auth-react-script/recipe/session";
supertokensUIInit({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
supertokensUIEmailPassword.init({
// highlight-start
onHandleEvent: async (context) => {
if (context.action === "PASSWORD_RESET_SUCCESSFUL") {
// Add you custom logic here
} else if (context.action === "RESET_PASSWORD_EMAIL_SENT") {
// Add you custom logic here
}
}
// highlight-end
}),
supertokensUISession.init()
]
});</FrontendPrebuiltUITabs.TabItem>
:::caution Not applicable
This section is not applicable for custom UI since you are calling the sign in API yourself anyway. You can perform anything you want to do during the password reset flow based on the result of the API call.
:::
Backend override
Overriding the passwordResetPOST function allows you to introduce your own logic for the password reset process.
Use it to introduce your own logic for the flow.
import SuperTokens from "supertokens-node";
import EmailPassword from "supertokens-node/recipe/emailpassword";
import Session from "supertokens-node/recipe/session";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
recipeList: [
EmailPassword.init({
// highlight-start
override: {
apis: (originalImplementation) => {
return {
...originalImplementation,
passwordResetPOST: async function(input) {
if (originalImplementation.passwordResetPOST === undefined) {
throw Error("Should never come here");
}
// First we call the original implementation
let response = await originalImplementation.passwordResetPOST(input);
// Then we check if it was successfully completed
if (response.status === "OK") {
// TODO: post password reset logic
}
return response;
}
};
},
},
// highlight-end
}),
Session.init()
]
});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{
//highlight-start
Override: &epmodels.OverrideStruct{
APIs: func(originalImplementation epmodels.APIInterface) epmodels.APIInterface {
// first we copy the original implementation
originalPasswordResetPOST := *originalImplementation.PasswordResetPOST
// override the password reset API
(*originalImplementation.PasswordResetPOST) = func(formFields []epmodels.TypeFormField, token string, tenantId string, options epmodels.APIOptions, userContext supertokens.UserContext) (epmodels.ResetPasswordPOSTResponse, error) {
// First we call the original implementation
resp, err := originalPasswordResetPOST(formFields, token, tenantId, options, userContext)
if err != nil {
return epmodels.ResetPasswordPOSTResponse{}, err
}
// Then we check if it was successfully completed
if resp.OK != nil {
// TODO: post password reset logic
}
return resp, nil
}
return originalImplementation
},
},
//highlight-end
}),
},
})
}from supertokens_python import init, InputAppInfo
from supertokens_python.recipe import emailpassword
from supertokens_python.recipe.emailpassword.interfaces import (
APIInterface,
PasswordResetPostOkResult,
)
from supertokens_python.recipe.emailpassword.interfaces import APIOptions
from supertokens_python.recipe.emailpassword.types import FormField
from typing import Dict, List, Any
# highlight-start
def override_apis(original_implementation: APIInterface):
original_password_reset_post = original_implementation.password_reset_post
async def password_reset_post(
form_fields: List[FormField],
token: str,
tenant_id: str,
api_options: APIOptions,
user_context: Dict[str, Any],
):
response = await original_password_reset_post(
form_fields, token, tenant_id, api_options, user_context
)
# Then we check if it was successfully completed
if isinstance(response, PasswordResetPostOkResult):
pass # TODO: post password reset logic
return response
original_implementation.password_reset_post = password_reset_post
return original_implementation
# highlight-end
init(
app_info=InputAppInfo(api_domain="...", app_name="...", website_domain="..."),
framework="...", # type: ignore
recipe_list=[
emailpassword.init(
# highlight-start
override=emailpassword.InputOverrideConfig(apis=override_apis)
# highlight-end
)
],
)See also
<ReferenceCard.Grid>
</ReferenceCard.Grid>
File: docs/authentication/email-password/implement-username-login.mdx
title: Implement username login hide_title: true sidebar_position: 7 toc_max_heading_level: 4 Enable username login by replacing email with username in the SuperTokens authentication flow. page_type: guide recipe: emailpassword category: email-password
Implement username login
Overview
This tutorial shows you how to customize the recipe to add username based login with an optional email field.
A few variations exist on how username-based flows can work:
| Login Type | Description | Password Reset Flow |
|---|---|---|
| Username only | User signs up and signs in with username and password | Contact support required |
| Username with optional email | User signs up with username and password, email is optional. Can sign in with either username or email | Uses email if provided, otherwise contact support |
| Username and email required | User must provide username, email and password during sign up. Can sign in with either username or email | Uses email |
This guide implements the second flow: Username and password login with optional email.
If you are using one of the other options, you can still follow this guide and make tweaks on parts of it to achieve your desired flow.
The approach is to update the email form field.
This way it gets displayed and validated as a username.
Then the optional email value gets saved against the userID of the user and you use it during sign in and reset password flows. You need to handle the mapping of email to userID and store it in your own database. The code snippets below create placeholder functions for you to implement.
Before you start
This guide assumes that you have already implemented the EmailPassword recipe and have a working application integrated with SuperTokens.
If you have not, please check the Quickstart Guide.
Steps
1. Modify the default email validator function
Update the backend validator function to check for your username format.
The function runs during sign up, sign in, and reset password.
Hence it needs to also match an email format since the user might enter it when signing in or resetting their password.
Inside SuperTokens, the field is still called email.
This ensures that the username is unique and that the authentication flows works.
Use the next code snippet as a reference.
import SuperTokens from "supertokens-node";
import EmailPassword from "supertokens-node/recipe/emailpassword";
SuperTokens.init({
appInfo: {
apiDomain: "...",
appName: "...",
websiteDomain: "..."
},
supertokens: {
connectionURI: "...",
},
recipeList: [
EmailPassword.init({
signUpFeature: {
formFields: [{
id: "email",
validate: async (value) => {
if (typeof value !== "string") {
return "Please provide a string input."
}
// first we check for if it's an email
if (
value.match(
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
) !== null
) {
return undefined;
}
// since it's not an email, we check for if it's a correct username
if (value.length < 3) {
return "Usernames must be at least 3 characters long."
}
if (!value.match(/^[a-z0-9_-]+$/)) {
return "Username must contain only alphanumeric, underscore or hyphen characters."
}
}
}]
}
})
]
});import (
"regexp"
"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{
SignUpFeature: &epmodels.TypeInputSignUp{
FormFields: []epmodels.TypeInputFormField{
{
ID: "email",
Validate: func(value interface{}, tenantId string) *string {
// first we check if the input is an email
emailCheck, err := regexp.Match(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, []byte(value.(string)))
if err != nil {
msg := "Email is invalid"
return &msg
}
if emailCheck {
return nil
}
// since it's not an email, we check for if it's a correct username
if len(value.(string)) < 3 {
msg := "Usernames must be at least 3 characters long."
return &msg
}
userNameCheck, err := regexp.Match(`^[a-z0-9_-]+$`, []byte(value.(string)))
if err != nil || !userNameCheck {
msg := "Username must contain only alphanumeric, underscore or hyphen characters."
return &msg
}
return nil
},
},
},
},
}),
},
})
}from re import fullmatch
from supertokens_python import InputAppInfo, init
from supertokens_python.recipe import emailpassword
from supertokens_python.recipe.emailpassword.types import InputFormField
from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature
async def validate(value: str, tenant_id: str):
# first we check for if it's an email
if fullmatch(
r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$',
value
) is not None:
return None
# since it's not an email, we check for if it's a correct username
if len(value) < 3:
return "Usernames must be at least 3 characters long."
if fullmatch(r'^[a-z0-9_-]+$', value) is None:
return "Username must contain only alphanumeric, underscore or hyphen characters."
return None
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...', # type: ignore
recipe_list=[
emailpassword.init(
sign_up_feature=InputSignUpFeature(form_fields=[
InputFormField(id="email", validate=validate)
])
)
]
)2. Save the user email
2.1 Update the sign up form validation
The sign up API takes in the username, password, and an optional email.
Add a new form field for the email, along with a validate function that checks the uniqueness and syntax of the input email.
:::warning Custom Implementation
To check if the email is unique you need to persist values in your own database and then check against them.
:::
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */from re import fullmatch
from typing import Dict
from supertokens_python import InputAppInfo, init
from supertokens_python.recipe import emailpassword
from supertokens_python.recipe.emailpassword.types import InputFormField
from supertokens_python.recipe.emailpassword.utils import InputSignUpFeature
email_user_map: Dict[str, str] = {}
# highlight-start
async def get_user_using_email(email: str):
# TODO: Check your database for if the email is associated with a user
# and return that user ID if it is.
# this is just a placeholder implementation
if email in email_user_map:
return email_user_map[email]
return None
# highlight-end
async def validate(value: str, tenant_id: str):
# from previous code snippet..
return None
async def validate_actual_email(value: str, tenant_id: str):
if fullmatch(
r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$',
value
) is None:
return "Email is invalid"
if (await get_user_using_email(value)) is not None:
return "Email already in use. Please sign in, or use another email"
init(
app_info=InputAppInfo(
api_domain="...", app_name="...", website_domain="..."),
framework='...', # type: ignore
recipe_list=[
emailpassword.init(
sign_up_feature=InputSignUpFeature(form_fields=[
InputFormField(id="email", validate=validate),
# highlight-start
InputFormField(id="actualEmail",
validate=validate_actual_email, optional=True)
# highlight-end
])
)
]
)2.2 Save the email field value
Override the sign up API to save the custom email form field.
Use a mapping of userID to email to keep track of the association.
Save the email value in your own database.
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */3. Allow username or email during sign in
The user should be able to sign in using their email or username along with their password.
In the new logic, if a user enters their email, you need to fetch the username associated with that email and then perform the authentication flow.
Override the sign in recipe function to allow this.
Use the next code snippet as a reference. The example use the email to userId mapping, mentioned earlier, to figure out which username to use.
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */4. Allow username or email during password reset
The password reset flow requires the user to have added an email during sign up.
If there is no email associated with the user, return an appropriate message.
To update the functionality you have to first change how the password reset token gets generated and then update the email sending logic.
This way both methods take into account the new fields.
4.1 Override the token generation API
The user should enter either their username or their email when starting the password reset flow.
Like the sign in customization, you must check if the input is an email and, if it is, retrieve the username associated with the email.
If you can't find a username from an email you have to return an appropriate message to the frontend.
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */4.2 Override the email sending API
Update the email sending API to retrieve the user email if the user used a username in the password reset flow.
/* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. *//* Detailed source-code truncated for AI context efficiency. */5. Show the new fields in the user interface
:::info
The following instructions are only relevant if you are using the pre-built UI.
If you created your own custom UI on the frontend, please make sure to pass the new email formField when you call the sign up function.
Even if the user has not given an email, you must add it with an empty string.
:::
Update the pre-built UI to reflect the new flow:
- Skip frontend validation for the
emailfield since username or email is permissible. The backend performs those checks. - Change the "Email" label in the sign up form to say "Username".
- Add an extra field in the sign up form where the user can enter their email.
- Change the "Email" label in the sign in form to say "Username or email".
- Change the "Email" label in the password reset form to say "Username or email".
- Update translations for the email field if necessary.
import SuperTokens from "supertokens-auth-react"
import EmailPassword from "supertokens-auth-react/recipe/emailpassword"
SuperTokens.init({
appInfo: {
appName: "...",
apiDomain: "...",
websiteDomain: "...",
},
// highlight-start
languageTranslations: {
translations: {
"en": {
EMAIL_PASSWORD_EMAIL_LABEL: "Username or email"
}
}
},
// highlight-end
recipeList: [
EmailPassword.init({
signInAndUpFeature: {
// highlight-start
signInForm: {
formFields: [{
id: "email",
label: "Username or email",
placeholder: "Username or email"
}]
},
signUpForm: {
formFields: [{
id: "email",
label: "Username",
placeholder: "Username",
validate: async (input) => {
// the backend validates this anyway. So nothing required here
return undefined;
}
}, {
id: "actualEmail",
validate: async (input) => {
// the backend validates this anyway. So nothing required here
return undefined
},
label: "Email",
optional: true
}]
}
// highlight-end
}
}),
// other recipes initialisation..
],
});See also
<ReferenceCard.Grid>
</ReferenceCard.Grid>
File: docs/authentication/email-password/introduction.mdx
title: Introduction hide_title: true sidebar_position: 1 pagination_next: null pagination_prev: null skip_llms_txt: true Authenticate users with email/password, customize UI, and explore SDK integration options. page_type: overview recipe: emailpassword category: email-password
Introduction
Overview
The Email/Password recipe provides a way of authenticating users with basic credentials.
You can use it out of the box, with the Pre-Built UI, or implement your own interface through the available SDKs.
Getting started
You can either follow the quickstart tutorial or use the CLI tool to generate an example app that shows you how the recipe works.
<ReferenceCard.Grid>
<ReferenceCard.Title>
Quickstart
</ReferenceCard.Title>
<ReferenceCard.Description>
Go through a quick tutorial that shows you how to add the Email/Password recipe to your app.
</ReferenceCard.Description>
<ReferenceCard.Title>
Example Applications
</ReferenceCard.Title>
<ReferenceCard.Description>
Use the CLI to generate a boilerplate app that you can use as a starting point.
</ReferenceCard.Description>
</ReferenceCard.Grid>
Customization
To adjust the functionality to fit your use case you can explore different sections from the documentation.
<ReferenceCard.Grid>
<ReferenceCard.Title>
Sign In Form Customization
</ReferenceCard.Title>
<ReferenceCard.Description>
Adapt the look and feel of the pre-built sign in form.
</ReferenceCard.Description>
<ReferenceCard.Title>
Sign Up Form Customization
</ReferenceCard.Title>
<ReferenceCard.Description>
Adapt the look and feel of the pre-built sign up form.
</ReferenceCard.Description>
<ReferenceCard.Title>
Hooks and Overrides
</ReferenceCard.Title>
<ReferenceCard.Description>
Add custom logic after the user logs in or signs up.
</ReferenceCard.Description>
<ReferenceCard.Title>
Password Hashing
</ReferenceCard.Title>
<ReferenceCard.Description>
Read which hashing algorithms you can use and how to use them.
</ReferenceCard.Description>
<ReferenceCard.Title>
Username Login
</ReferenceCard.Title>
<ReferenceCard.Description>
Discover how you can implement an authentication flow that makes use of usernames instead of email addresses.
</ReferenceCard.Description>
</ReferenceCard.Grid>