Sign up users easily with SuperTokens: Email Password Authentication

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

Ā·

15 min read

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.

  1. 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.
  2. 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.

3 basic screens

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;

Home Screen preview with Login Screen Preview: Login Screen only has Sign up component for now

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.

The UI shows the db connection settings

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.

![The modal shows the files you have chosen to import and you have an option to import or cancel](cdn.hashnode.com/res/hashnode/image/upload/..

After importing, you can see many of the examples that we see in the SwaggerHub. Some of these may not be 100% accurate, so do refer back to the SwaggerHub and adjust as required.

Postman Collection & API is created after importing

Update the baseUrl variable at the collectionā€™s variables to where your server is running.

If your backend server is still running, you can try running the API for sign up user. You can see that the API call did go through, and a validation error is returned. This is one of the cases where the example does not match the schema that is currently shown in SwaggerHub.

If we update the request body to match what the /{apiBasePath}/signup endpoint requires as shown on the SwaggerHub, now we can call the API to see that the user is created successfully.

The API works! But how do we make sure that the user is really created? Now, letā€™s learn how to connect to the Supertokens DB.

Connecting to the PostgreSQL DB

While SuperTokens manage and abstracts away many things for you, itā€™s good to learn how to connect to the DB that contains all the tokens, session and user information for various use cases:

  • Modify usersā€™ personal information such as email address
  • Test the login/account creation functionality by intentionally dropping users
  • Test local cache/ session management by intentionally dropping tokens instead of manually logging out and in

To access the PostgreSQL DB, click on the blue text that you previously saw on the dashboard for your managed service and a modal will pop up.

supertokens modal showing db connection settings

To connect to SQL databases, I usually use DBeaver. You can use any other GUI or CLI. Hereā€™s a visual example of the connection settings window where you would throw the db connection string and credential values above.

the host, database, username and password fields are filled with values you get from the supertokens dashboard

After connecting, you will be able to find the following tables.

you will be able to see tables such as all_auth_recipe_users, emailpassword_users, session_info etc

For us to know that the request that we made to sign up a user through the API did work as intended, here we can go into the emailpassword_users table.

A record for the user that we just created in the db

Here, you can see our userā€™s id, email, password hash & time joined āœØ

Bringing it all together

Now that we have confirmed that we can create users by calling the SuperTokens API using Postman, letā€™s do the same but using the mobile frontend instead.

Create a Sign up function

šŸ’” Pro Tip: Postman has a Code Snippet feature where you can choose a code snippet type for calling an API. Hence, we could utilize this feature to generate a JavaScript fetch code snippet for us rather than writing out the headers, body etc from scratch.

But we donā€™t need the extra session token stuff in here, so we can remove that portion. This leads us to create a sign up function as such.

const signUpUser = ({
  email,
  password,
}: {
  email: string;
  password: string;
}): 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.

    Alert showing user created text with user id

  • 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! šŸŽ‰

birds excited

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.

Did you find this article valuable?

Support Estee Tey by becoming a sponsor. Any amount is appreciated!

Ā