Sign up users easily with SuperTokens: Email Password Authentication
Learn to build a simple app with SuperTokens authentication on a Typescript project with React Native & Node.js Express š Part 1: User Sign Up
Table of contents
Supertokens is an open source user authentication library that is quick to implement and easy to customize for developers. In this series, I will teach you how to add email password authentication based on SuperTokens into a project where the tech stack is:
- Frontend App ā React Native (Typescript)
- Backend Server ā NodeJS
This article will be focused on creating a MVP to sign up a user.
Introduction to the project
Our project will be a monorepo that contains both the frontend and backend code. The app that we will be creating will be very simple, which you can easily adapt and extend the code here to fit in your own project to include email password authentication āØ
There will just be 2 screens: Home & Login.
- Home Screen: shows whether user is logged in or not, and if the user is logged in, it will also show user & session related details.
- Login screen: User can sign up or sign in here. The UI here tries to follow the style of the Supertokens React Login, but feel free to create your own custom login component.
The projectās source code for this article can be found at this GitHub repository for your reference.
Prerequisites (good to have)
Meeting the prerequisites below will help to understand faster what is going on, but if you donāt meet any of them, donāt worry about it. You should still be able to follow along š
- Basics of React Native
- Basics of calling an API
- Basics of SQL databases
Letās begin with the frontend section first.
Frontend.init()
Create a React Native app from template
For the frontend, letās spin up a fresh new React Native (RN) app easily with a template.
npx react-native init demoapp --template react-native-template-typescript
Then install the following packages. We will be using react-native-paper
components in the app for ease of development, but feel free to create your own custom components.
npm install supertokens-react-native \
@react-native-async-storage/async-storage \
@react-navigation/native \
@react-navigation/native-stack \
react-native-screens \
react-native-safe-area-context \
react-native-paper
Then we have to install the pods
npx pod-install
To install and start the app, you can choose to run one of the following commands in the terminal.
npm run android # starts the android emulator and the metro server & installs app
npm run ios # starts the ios emulator and the metro server & installs app
Then we can create components for the two screens that we want to have in the app, for now they will just have placeholder content in them.
screens/HomeScreen.tsx
import React from 'react';
import {SafeAreaView, Text} from 'react-native';
const HomeScreen: React.FC = ({}) => {
return (
<SafeAreaView>
<Text>HomeScreen in WIP</Text>
</SafeAreaView>
);
};
export default HomeScreen;
screens/LoginScreen.tsx
import React from 'react';
import {SafeAreaView, Text} from 'react-native';
const LoginScreen: React.FC = ({}) => {
return (
<SafeAreaView>
<Text>LoginScreen in WIP</Text>
</SafeAreaView>
);
};
export default LoginScreen;
Set up Navigation in the app
We will create a file routes.ts
file to contain the types required for navigation.
export type RootStackParamList = {
Home: undefined;
Login: undefined;
};
export type NavigationProps = NativeStackNavigationProp<RootStackParamList>;
Then, we will remove all the template UI code at App.tsx
so that the it will just be a wrapper component for the SafeAreaProvider
and NavigationContainer
.
App.tsx
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import {RootStackParamList} from './routes';
import HomeScreen from './screens/HomeScreen';
import LoginScreen from './screens/LoginScreen';
const RootStack = createNativeStackNavigator<RootStackParamList>();
const NavStack = () => {
return (
<RootStack.Navigator initialRouteName="Home">
<RootStack.Screen name={'Login'} component={LoginScreen} />
<RootStack.Screen name={'Home'} component={HomeScreen} />
</RootStack.Navigator>
);
};
const App = () => {
return (
<SafeAreaProvider>
<NavigationContainer>
<NavStack />
</NavigationContainer>
</SafeAreaProvider>
);
};
export default App;
We would also want the user to be able to go to the login screen from the home screen and vice versa. For now, letās create a simple button for navigation on Home Screen.
screens/HomeScreen.tsx
import {useNavigation} from '@react-navigation/native';
import * as React from 'react';
import {StyleSheet} from 'react-native';
import {Button, Text} from 'react-native-paper';
import {SafeAreaView} from 'react-native-safe-area-context';
import {NavigationProps} from '../routes';
const HomeScreen: React.FC = () => {
const navigator = useNavigation<NavigationProps>();
return (
<SafeAreaView style={styles.container}>
<Text>You have not logged in yet.</Text>
<Button
onPress={() => {
navigator.navigate('Login');
}}>
Log in
</Button>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 8,
},
});
export default HomeScreen;
When you click on the login button, you will be directed to the login screen, and you will also be able to navigate back by clicking the back button that is set up by the @react-navigation/native
library.
Next, letās create a proper login UI.
At the time of publishing this article, there isnāt a React Native Login component out of the box yet for SuperTokens RN SDK, so we will have to create our own UI for login. Iāll try to create it and add it to the official supertokens-react-native SDK soon š¤ The next portion is still helpful if you intend to create a custom UI.
Create the SignUp component for Login Screen
components/SignUp.tsx
import * as React from 'react';
import {Linking, StyleSheet, View} from 'react-native';
import {
Button,
Caption,
Card,
Divider,
Headline,
Subheading,
Text,
TextInput,
} from 'react-native-paper';
const SignUpComponent: React.FC = () => {
return (
<Card style={styles.card}>
<View style={styles.textContainer}>
<Headline>Sign up</Headline>
<Subheading>
Already have an account? <Text style={styles.urlText}>Sign In</Text>
</Subheading>
</View>
<Divider style={styles.divider} />
<TextInput style={styles.input} label="Email Address" />
<TextInput style={styles.input} label="Password" secureTextEntry />
<Button mode="contained" color="#ff9933">
Sign up
</Button>
<View style={styles.textContainer}>
<Caption>
By signing up, you agree to our{' '}
<Text
style={styles.urlText}
onPress={() =>
Linking.openURL(
'https://supertokens.com/legal/terms-and-conditions',
)
}>
Terms of Service
</Text>{' '}
and{' '}
<Text
style={styles.urlText}
onPress={() => {
Linking.openURL('https://supertokens.com/legal/privacy-policy');
}}>
Privacy Policy
</Text>
</Caption>
</View>
</Card>
);
};
const styles = StyleSheet.create({
card: {
padding: 16,
},
urlText: {
color: 'blue',
},
textContainer: {
alignItems: 'center',
textAlign: 'center',
},
divider: {
margin: 12,
},
input: {
marginVertical: 12,
},
});
Then we can show the SignUp component in the Login Screen.
import * as React from 'react';
import {SafeAreaView} from 'react-native-safe-area-context';
import SignUpComponent from '../components/SignUp';
const LoginScreen: React.FC = () => {
return (
<SafeAreaView>
<SignUpComponent />
</SafeAreaView>
);
};
export default LoginScreen;
Now that we have the UI up for the login screen, we want the app to actually create a user session for them when they click on the login and create account buttons. Then, we can add the Supertokens functionality to the buttons.
Supertokens.init()
By initialising SuperTokens on the frontend, Supertokens helps us to automatically add request interceptors to save & store tokens for persisting the user sessions. To initialize the Supertokens client on the RN app, we can add the following code block at the top of the App.tsx
before the <App />
component is initialised.
import SuperTokens from 'supertokens-react-native';
const apiDomain = 'http://192.168.1.3:3001'; // TODO: change url to your own IP address
SuperTokens.init({
apiDomain,
apiBasePath: '/auth',
});
To figure out your own IP address, you can use the bash command
ifconfig | sed -En 's/127.0.0.1//;s/.*inet (addr:)?(([0-9]*\.){3}[0-9]*).*/\2/p'
Now that we have an app that we can interact with, we can start tackling the backend portion.
Backend.init()
To run Supertokens, we have 2 choices:
- self-hosted
- host on SuperTokens
Here, Iāll show you how we can create a Supertokens managed service.
Create a SuperTokens Managed Service
To create a SuperTokens Managed Service, click on the blue option where you be directed to an account creation page. Next, you can follow the instructions to choose an availability region for instantiating your managed service.
After creating a SuperTokens Managed Service, you will see the following UI where a development environment is created by default.
You will notice thereās also a blue text āAccess the PostgreSQL database used by this coreā, we will cover that in a later section. For now, letās create a backend server that can connect to this managed service.
Create a NodeJS express backend server
For the backend, create a file server/index.ts
and install the following packages
npm install supertokens-node express cors
npm install -D @types/cor nodemon ts-node
SuperTokens offers a list of recipes for you to cook up different auth side dishes to complement the features that make up the main course of your app. In this article, we will be using the EmailPassword & Session Recipe. Hence, letās follow that recipe (backend) to initialise our Supertokens client on the backend server. This leads our server code to be as such.
import cors from 'cors';
import express from 'express';
import {errorHandler, middleware} from 'supertokens-node/framework/express';
import supertokens from 'supertokens-node';
import Session from 'supertokens-node/recipe/session';
import EmailPassword from 'supertokens-node/recipe/emailpassword';
const apiPort = 3001;
const apiDomain = 'http://localhost:' + apiPort;
supertokens.init({
framework: 'express',
supertokens: {
// Populate the fields below with the connection details of the app you created on supertokens.com
connectionURI: "https://sth.aws.supertokens.io:3568",
apiKey: "heheSomeSecret",
},
appInfo: {
appName: 'Demo App',
apiDomain,
websiteDomain: 'http://localhost:3000', // our mobile app metro server runns on this domain
},
recipeList: [
EmailPassword.init(), // initializes signin, sign up features
Session.init(), // initializes session features
],
});
const app = express();
app.use(
cors({
origin: 'http://localhost:3000',
allowedHeaders: ['content-type', ...supertokens.getAllCORSHeaders()],
credentials: true,
}),
);
app.use(middleware());
app.use(express.json());
app.use(errorHandler());
app.listen(apiPort, () => {
console.log('server started');
});
š” Supertokens library is written in Typescript. You can easily check the parameters that
supertokens.init()
is expecting in your code editor If you are lazy to look through the documentation on the browser.
Then, we can add a script in package.json to start up the express server conveniently. To make it easier to distinguish between the 2 start scripts for frontend and backend respectively, we can make that the suffix.
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start:frontend": "react-native start",
"start:backend": "nodemon server/index.ts",
"test": "jest",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
},
We can then start the backend app by running the npm command in a separate terminal
npm run start:backend
Now, we have both the frontend and backend up šµ That also means we are almost done! Before we hook up the buttons to call the Supertokens API to perform user creation & login, letās play with the Supertokens API to understand how it works.
Play with Supertokens API in Postman
ā Before we continue, if youāre not familiar with the distinction between Application Programming Interface (API) and Software Development Kit (SDK), you can read the below description block. Otherwise, you can skip it and continue with the guide.
Based on the definition given by Axway,
āAn API is a set of libraries that make up the core language that you can work with out of the box, whereas an SDK is a development kit that facilitates usages of an API. Conceptually both are a way for your program to interface with and control the resources provided by another piece of software.ā
This means that software services usually deliver features through their APIs first before offering the same through the SDKs. This is also the case for Supertokens, so you may notice at the time of publishing this article, most of the Supertokens frontend SDKs donāt offer all of the features that are available through their Frontend Driver Interface API.
Hereās an in-depth article on the differences of SDK vs API if youāre keen to read up more.
The SuperTokens API Reference is split into 2 parts:
- 1 for the backend drivers (core driver) e.g.
supertokens-node
- 1 for the frontend drivers e.g.
supertokens-react-native
These APIs are exposed through SwaggerHub, where you can see the OpenAPI specs and that the endpoints are grouped nicely based on recipes.
š” Pro tip: you could download the OpenAPI specs here and import it into Postman āØ
To download the JSON, go to Export > Download API > JSON Resolved.
Then you can import the file by going to File > Import. Choose the JSON file and you will see the below modal.
: Promise<any> => {
const myHeaders = new Headers();
myHeaders.append('rid', 'emailpassword');
myHeaders.append('Content-Type', 'application/json');
const raw = JSON.stringify({
formFields: [
{
id: 'email',
value: email,
},
{
id: 'password',
value: password,
},
],
});
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow',
};
return fetch('http://localhost:3001/auth/signup', requestOptions);
};
Pass user inputs to the SuperTokens API
Next, we will need to save the user input values first before we can pass those values to this sign up function. Letās create a state variable that we can set at the LoginScreen
, and pass it down to the SignUpComponent
.
import * as React from 'react';
import {SafeAreaView} from 'react-native-safe-area-context';
import SignUpComponent from '../components/SignUp';
export interface LoginComponentProps {
email: string;
password: string;
setEmail: React.Dispatch<React.SetStateAction<string>>;
setPassword: React.Dispatch<React.SetStateAction<string>>;
}
const LoginScreen: React.FC = () => {
const [email, setEmail] = React.useState<string>('');
const [password, setPassword] = React.useState<string>('');
const loginComponentProps: LoginComponentProps = {
email,
password,
setEmail,
setPassword,
};
return (
<SafeAreaView>
<SignUpComponent {...loginComponentProps} />
</SafeAreaView>
);
};
export default LoginScreen;
Then we can type the props that SignUpComponent
accepts, and also set the value
and onChangeText
props for the user inputs.
const SignUpComponent: React.FC<LoginComponentProps> = ({
email,
password,
setEmail,
setPassword,
}) => {
const navigator = useNavigation<NavigationProps>();
return (
<Card style={styles.card}>
<View style={styles.textContainer}>
<Headline>Sign up</Headline>
<Subheading>
Already have an account? <Text style={styles.urlText}>Sign In</Text>
</Subheading>
</View>
<Divider style={styles.divider} />
<TextInput
style={styles.input}
label="Email Address"
value={email}
onChangeText={setEmail}
/>
<TextInput
style={styles.input}
label="Password"
secureTextEntry
value={password}
onChangeText={setPassword}
/>
<Button
mode="contained"
color="#ff9933"
>
Sign up
</Button>
<View style={styles.textContainer}>
<Caption>
By signing up, you agree to our{' '}
<Text
style={styles.urlText}
onPress={() =>
Linking.openURL(
'https://supertokens.com/legal/terms-and-conditions',
)
}>
Terms of Service
</Text>{' '}
and{' '}
<Text
style={styles.urlText}
onPress={() => {
Linking.openURL('https://supertokens.com/legal/privacy-policy');
}}>
Privacy Policy
</Text>
</Caption>
</View>
</Card>
);
};
Finally, we can call the signUpUser()
function when the user clicks on the sign up button. And to make it more obvious on the app that we did create the user,
- we will show a simple alert with the created userās id.
- when user clicks the OK button on the alert, we will also redirect the user back to the home page. This leads to the sign up button code to be as such.
To do these, we just have to put these functionalities to the callback for the onPress
prop.
<Button
mode="contained"
color="#ff9933"
onPress={() => {
signUpUser({email, password})
.then(response => response.json())
.then(result => {
console.log(result);
Alert.alert('User created', `${result.user.id}`, [
{text: 'OK', onPress: () => navigator.navigate('Home')},
]);
})
.catch(error => console.log('error', error))
.finally(() => console.log('called Supertokens API'));
}}>
Sign up
</Button>
When the user is created successfully,
This is the alert that the user can see.
We can also see the console logs that we added.
LOG {"status": "OK", "user": {"email": "st@supertokens.com", "id": "f672a1c1-dda4-419d-8232-5b565a0a0fc8", "timeJoined": 1645034947404}} LOG called Supertokens API
š Great job! We're done.
If you ever got lost on the code, refer to the GitHub repository.
Here's the stuff you have learnt in this article to add sign up functionality to a project:
- Simple React Navigation with Typescript
- Initialising SuperTokens clients on both the frontend and backend
- Navigate SuperTokens API reference and call SuperTokens API to sign up a user
- Importing OpenAPI specs to Postman, and using the CodeSnippet feature to speed up your development
Of course, there's still a lot we can do to make our app better. After being redirected to the home page, our users don't see any useful information yet. In fact, they are not even logged in after creating their account (this is an undesirable behavior, but present in many apps because of prioritizations of other features š„).
In the next article, we will cover the steps on how to manage user sessions in React Native, including sign in and sign out. Look forward to it~ ⨠Before then, you can have a read at Abhinav's article on Effective Session Management to learn more about the concepts going behind the abstracted API.
That's a wrap folks! š
Thank you for reading, hope you enjoyed the article!
If you find the article awesome, hit the reactions š§” and share it š¦~
I'll also be working on a follow up to this article which will include a Spring Boot project example āØ
To stay updated whenever I post new stuff, follow me on Twitter.