Setting Up Auth0 OTP Authentication with an 8base Workspace

Using JWT Authentication on a GraphQL API with OTP Issued ID Tokens

Getting started with OTP Authentication

Creating an Auth0 Application with OTP Enabled

Creating an Auth0 app for OTP
Enabling Email OTP for auth0 application

Creating an Authentication Profile for Passwordless OTP in 8base

Creating authentication profile in 8base
Setting environment variables in 8base

Developing serverless functions for Passwordless OTP

# Authenticate your 8base CLI if not already done
8base login
# Create a new 8base project and connect it to your workspace
8base init passwordless-otp --empty
# Generate resolver for initiating passwordless auth
8base generate resolver passwordlessAuthStart --syntax=js
# Generate resolver for verifying auth code and returning token
8base generate resolver passwordlessAuthLogin --syntax=js
# Generate utils folder with shared script files.
mkdir src/utils && touch src/utils/auth0.js src/utils/graphql.js

src/utils/auth0.js

/* Import your preferred HTTP client */
import axios from 'axios'
/* Access the environment variables we stored in 8base */
const domain = process.env.AUTH0_DOMAIN
const client_id = process.env.AUTH0_CLIENT_ID
const client_secret = process.env.AUTH0_CLIENT_SECRET
/* Export default module with auth methods */
export default {
/**
* Initiate the OTP flow by sending user an email or link
* with a one-time passcode.
*
* Endpoint Docs: https://auth0.com/docs/connections/passwordless/reference/relevant-api-endpoints#post-passwordless-start
*/
otpStart: async (email, send = 'code') => {
return axios.post(`https://${domain}/passwordless/start`, {
connection: 'email',
client_secret,
client_id,
email,
send
})
},
/**
* Verify an issued token with the username (email)
* and receive back the auth result.
*
* Endpoint Docs: https://auth0.com/docs/connections/passwordless/reference/relevant-api-endpoints#post-oauth-token
*/
tokenVerify: async (username, otp) => {
return axios.post(`https://${domain}/oauth/token`, {
grant_type: 'http://auth0.com/oauth/grant-type/passwordless/otp',
realm: 'email',
client_secret,
client_id,
username,
otp
})
}
}

src/utils/graphql.js

import gql from 'graphql-tag'
/**
* Create a user record in 8base using a valid ID token
*/
export const USER_SIGN_UP_WITH_TOKEN = gql`
mutation($authProfileId: ID!, $email: String!) {
userSignUpWithToken(
authProfileId: $authProfileId
user: { email: $email }
) {
id
}
}
`
/**
* Query a user using their email address.
*/
export const FIND_USER_BY_EMAIL = gql`
query users($email: String) {
usersList(filter: { email: { equals: $email } }) {
count
}
}
`

passwordlessAuthStart

src/resolvers/passwordlessAuthStart/schema.graphql

type PasswordlessAuthStartResult {
success: Boolean!
}
extend type Mutation {
# Declare GraphQL mutation inputs and response
passwordlessAuthStart(email: String!, type: String): PasswordlessAuthStartResult
}‍

src/resolvers/passwordlessAuthStart/handler.js

import auth0 from '../../utils/auth0'/* Send a password reset email to the user */
export default async event => {
/* Unpack event data */
const { email, type = 'code' } = event.data
try {
/* Initiate the authentication flow by sending OTP email */
await auth0.otpStart(email, type)
} catch (error) {
/* Console error to logs */
console.error(error)
/* Return failure and error */
return {
data: {
success: false
},
errors: [error]
}
}
/* Return success result */
return {
data: {
success: true
}
}
}

src/resolvers/passwordlessAuthStart/mocks/request.json

{
"data": {
"email": "<YOUR_EMAIL_ADDRESS>"
}
}
AUTH0_DOMAIN=<YOUR_AUTH0_DOMAIN>
AUTH0_CLIENT_ID=<YOUR_AUTH0_CLIENT_ID>
AUTH_PROFILE_ID=<YOUR_AUTH_PROFILE_ID>
AUTH0_CLIENT_SECRET=<YOUR_AUTH0_CLIENT_SECRET>
$ env $(cat .env.local | xargs) 8base invoke-local passwordlessAuthStart -m request=> Result:
{
"data": {
"success": true
}
}

passwordlessAuthLogin

src/resolvers/passwordlessAuthLogin/schema.graphql

# Auth0 token response object
type AuthResult {
expires_in: Int
id_token: String
token_type: String
access_token: String
refresh_token: String
}
# Login response object
type PasswordlessAuthLoginResult {
success: Boolean!
auth: AuthResult
}
# Mutation accepts email and code inputs
extend type Mutation {
passwordlessAuthLogin(email: String!, code: String!): PasswordlessAuthLoginResult
}

src/resolvers/passwordlessAuthLogin/handler.js

import auth0 from '../../utils/auth0'
import { USER_SIGN_UP_WITH_TOKEN, FIND_USER_BY_EMAIL } from '../../utils/graphql'
/* Declare static constants */
const noPermissions = { checkPermissions: false }
const authProfileId = process.env.AUTH_PROFILE_ID
export default async (event, ctx) => {
/* Unpack event data */
const { email, code } = event.data
/* Set default response values */
let success = true
let auth = {}
try {
/* Validate email and code */
;({ data: auth } = await auth0.tokenVerify(email, code))
/* Find user record stored in 8base by email */
let {
usersList: { count }
} = await ctx.api.gqlRequest(FIND_USER_BY_EMAIL, { email }, noPermissions)
/**
* If the user doesn't exist, create the user
* using the issued token, email, and authProfileId.
*/
if (count) {
await ctx.api.gqlRequest(
USER_SIGN_UP_WITH_TOKEN,
{
authProfileId,
email
},
{
headers: {
/* Set the issued token as a bearer token in headers */
authorization: `Bearer ${auth.id_token}`
}
}
)
}
} catch (error) {
/* Console error to logs */
console.error(error)
/* Return failure and error */
return {
data: {
success: false
},
errors: [error]
}
}
/* Return success and auth result */
return {
data: {
success,
auth
}
}
}

src/resolvers/passwordlessAuthLogin/mocks/request.json

{
"data": {
"email": "<YOUR_EMAIL>",
"code": "<YOUR_OTP_CODE>"
}
}
$ env $(cat .env.local | xargs) 8base invoke-local passwordlessAuthLogin -m request=> Result:
{
"data": {
"success": true,
"auth": {
"expires_in": 86400,
"token_type": "Bearer",
"scope": "openid profile email address phone",
"access_token": "2L7f_9xf3hNp4bdkhdig38yg388yg8g8",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp.someTOKEN.XVCIsImtpZCI6IkJ0ZzQxWFVvNE1iQTZuWXZlQ1Np"
}
}
}‍

Deploying our Passwordless OTP Auth

$ 8base deploy=> deploy done. Time: 23,598 ms.

Passwordless Auth Start

mutation {
passwordlessAuthStart(email: "<YOUR_EMAIL>") {
success
}
}
{
"data": {
"passwordlessAuthStart": {
"success": true
}
}
}

Passwordless Auth Login

mutation {
passwordlessAuthLogin(
email: "<YOUR_EMAIL>",
code: "<SOME_CODE>"
) {
success
auth {
refresh_token
access_token
expires_in
token_type
id_token
}
}
}
{
"data": {
"passwordlessAuthLogin": {
"success": true,
"auth": {
"refresh_token": "some_refresh_token",
"access_token": "some_access_token",
"expires_in": 86400,
"token_type": "Bearer",
"id_token": "some.valid.idtoken"
}
}
}
}
mutation {
userRefreshToken(data: {
email: "<USER_EMAIL>"
refreshToken: "<ISSUED_REFRESH_TOKEN>"
authProfileId: "<YOUR_AUTH_PROFILE_ID>"
}) {
refreshToken
idToken
}
}

Conclusion

Loves art, writing, and code.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store