Authentication
  • 03 Oct 2021
  • 4 Minutes to read
  • Contributors
  • Dark
    Light
  • PDF

Authentication

  • Dark
    Light
  • PDF

Authentication in Chainstarters

Recently Chainstarters has leveraged Magic to integrate passwordless authentication out of the box. When you spin up a project with Chainstarters, both your developer team and end users will benefit from this simple authentication method.

Magic offers these login methods:

  • Email
  • Social
  • WebAuthn (Biometrics)
  • Blockchain (EVM)
  • SMS
  • Multi-factor Authenticaion

Go here to read the documentation from Magic

A note on the Chainstarters Dashboard. Soon you will see User Pool change to Authentication.

Let's take a look how we utilize Magic on the project level:

In our backend code, we store the magic integration code in the graphql/src/lib/magic/index.js file:

    const { Magic } = require('@magic-sdk/admin')
    const config = require('../../config')

    const magic = new Magic(config.magicSecretKey)

    const getMetadataByToken = (DIDToken) => magic.users.getMetadataByToken(DIDToken)
    const getIssuer = (DIDToken) => magic.token.getIssuer(DIDToken)
    const validate = (DIDToken) => magic.token.validate(DIDToken)

    module.exports = {
      getIssuer,
      validate,
      getMetadataByToken
    }

This code will give our authContext the methods it needs to validate the login and get access to the data needed for authentication.



Notice the DIDToken, DID is short for Decentralized ID. These DID Tokens are cryptographic proofs to manage user access. These proofs are encoded as a digital signature for sharing between client and server to:

  • Authenticate Users
  • Manage Permissions
  • Protect Routes and Resources

For a more detailed article on DID tokens, read the documentation here.

DID tokens leverage the Ethereum Blockchain, so we will reference Web3 later on the frontend.



Now let's take a look at the authContext.js file

    const { validate, getMetadataByToken } = require('../lib/magic')

    const authContext = async (token) => {
      try {
        if (!token) {
          throw new Error('Authorization headers not found.')
        }

        validate(token)
        const decoded = await getMetadataByToken(token)

        const user = {
          user_id: decoded.issuer,
          email: decoded.email,
          wallet_address: decoded.publicAddress
        }

        return { user, logged_in: true }
      } catch (err) {
        return { user: {}, logged_in: false }
      }
    }

    module.exports = authContext

Here you get to see the user object with what's needed to authenticate the user into your application.

If you have used the legacy Chainstarters platform, you'll notice that the user id is now user_id, if you are still using the legacy platform, you will notice in the Auth Context that your user id remains aws_user_id.


Now let's look at the frontend implementation of this new form of authentication:

For this example, we will use the React Web code.

The primary logic for authentcation on the frontend lives in src/components/AuthRoute/ and lib/magic.js

Let's start with the lib/magic.js file:

    import { Magic } from 'magic-sdk'
    import Web3 from 'web3'
    import config from '../config'

    const MAGIC_KEY = config.magic_pk
    const NODE_ADDRESS = config.eth.nodeAddress
    const CHAIN_ID = config.eth.chainId

    const customNodeOptions = {
      rpcUrl: NODE_ADDRESS,
      chainId: CHAIN_ID,
    }

    export const magic = new Magic(MAGIC_KEY, { network: customNodeOptions })
    export const web3 = new Web3(magic.rpcProvider)

    export const login = async (email, dispatch, hasCreatedAccount) => {
      let res = await magic.auth.loginWithMagicLink({ email })
      const did_token = await magic.user.generateIdToken({ lifespan: 86400 * 2 })
      localStorage.setItem('did_token', did_token)

      if (hasCreatedAccount) {
        dispatch({ type: 'login' })
        console.log("top")
      }
      else {
        dispatch({ type: 'onboard' })
        console.log("bottom")
      }
      return res
    }

    export const logout = async () => {
      await magic.user.logout()
      localStorage.clear('did_token');
      window.location.reload()

      return true
    }

    export const checkLoggedIn = async () => {
      const ret = await magic.user.isLoggedIn()

      return ret
    }

To utilize the Magic Provider on the frontend, we leverage Web3. Immediately the user will benefit from simply entering their email to login (which will send an email with a click to login button) - let res = await magic.auth.loginWithMagicLink({ email }). Using this RPC Provider is essentially used as a Web API. Also note the lifespan of the token as well as how we set the DID token in local storage.

We use Magic as an authentication for the user and then set the Global State of the application, and then redirect through React Router.

Here is the AuthRoute.js code:

    import React, { useEffect } from "react";
    import { Route, Redirect, useHistory  } from "react-router-dom";
    import { magic } from "../../lib/magic"
    import { useGlobalState } from '../../../src/lib/useGlobalState'


    export default function AuthRoute({ children, path, component: Component, isLoggedIn, onBoarding, ...rest }) {

        const [{ user: { logged_in, onboarding } }, dispatch] = useGlobalState()
        const history = useHistory();
        const onboardFlow = ['/login', '/onboard']

        useEffect(async () => {
            try {
                if (!localStorage.getItem('did_token')) {
                    const res = await magic.user.logout()
                    history.push("/login")
                }
            }
            catch(err) {
                console.log({err})
            } 
        }, [])


        if (!logged_in) {
          return (
            <Route
              path={path}
            >
              <Redirect to="/login" />
              { children }
            </Route>)
        }
        else if (onboarding) {
          return (
            <Route
              path={path}
            >
              <Redirect to="/onboard" />
              { children }
            </Route>)
        }
        else {
          if (onboardFlow.includes(path)) {
            return (
              <Route
                path={path}
              >
                <Redirect to="/" />
                { children}
              </Route>
              )
          }
          else {
            return (
              <Route
                path={path}
              >
                { children}
              </Route>
              )
          }
        }
    };


Keep in mind that you do have the option to use AWS Cognito, but we strongly recommend using Magic for authentication. Magic offers a far easier and secure authentication experience with minimal dev effort.


Was this article helpful?

What's Next