Skip to content

Instantly share code, notes, and snippets.

@syardumi
Last active July 10, 2020 16:53
Show Gist options
  • Save syardumi/4b1f371ad53a4bfaac1db9065c414d61 to your computer and use it in GitHub Desktop.
Save syardumi/4b1f371ad53a4bfaac1db9065c414d61 to your computer and use it in GitHub Desktop.
  • Follow demo.sh for Amplify & Auth setup
    • Create Create/Define/VerifyAuthChallenge JS files at line 57 of demo.sh (only for custom auth flow in Amplify)
    • If you are following custom auth flow, you can have the user email auto-verified on sign-up
      • Create lambda function with the PreSignUp.js code
      • Go to Cognito -> your User Pool -> General settings -> Triggers -> Pre sign-up: choose the lambda function from the step above
  • Use authFuncs.js as a starting point for application integration with the Amplify Auth setup
import { CognitoUser } from 'amazon-cognito-identity-js'
import { Amplify, Auth } from 'aws-amplify'
import 'react-native-get-random-values'
import { v4 as uuidv4 } from 'uuid'
import awsconfig from './aws-exports' //auto-generated by amplify init/add auth
Amplify.configure(awsconfig)
let cognitoUser: CognitoUser | undefined
const authFuncs = () => ({
/**
* createCode -> signIn
*/
createCode: async (email: string) => {
try {
//try signing in with just email
cognitoUser = await Auth.signIn(email.toLowerCase());
} catch (err) {
console.log(err);
//if this user doesn't exist in the cognito user pool yet, create it (a pre-signup lambda can auto-verify the email address)
await Auth.signUp({
username: email.toLowerCase(),
password: uuidv4(),
attributes: {
email: email.toLowerCase(),
},
});
//once the user is created, then sign them in
cognitoUser = await Auth.signIn(email.toLowerCase());
}
},
signIn: async (code: any, callback: () => void) => {
if (cognitoUser) {
//send in the code answer
cognitoUser = await Auth.sendCustomChallengeAnswer(cognitoUser, code);
try {
// This will throw an error if the user is not yet authenticated
const data = await Auth.currentSession();
//TODO: if it passes, log us in and save the access token
callback();
} catch {
console.log("Apparently the user did not enter the right code");
//TODO: show an error message on screen
}
}
},
/**
* facebookPopUp -> facebookSignIn
*/
facebookPopUp: async () => {
await Auth.federatedSignIn({ provider: "Facebook" });
},
facebookSignIn: async () => {
//TODO: save access type as 'FACEBOOK' and any access token we get from OAuth
},
/**
* signOut
*/
signOut: async (willDoLogic: boolean) => {
if (willDoLogic) {
//purge user
cognitoUser = undefined
//sign out of amplify
await Auth.signOut()
}
if (state.accessType !== 'FACEBOOK') {
//TODO: if not FB OAuth, update state
}
}
});
const cryptoRandomString = require('crypto-random-string')
const ses = new (require('aws-sdk').SES)()
const sendEmail = async (emailAddress, secretLoginCode) => {
const params = {
Destination: { ToAddresses: [emailAddress] },
Message: {
Body: {
Html: {
Charset: 'UTF-8',
Data: `<html><body><p>This is your Adieu login code:</p><h3>${secretLoginCode}</h3><p>...or <a href="${process.env.LOGINREDIRECTURL + secretLoginCode}">follow this link</a></p></body></html>`
},
Text: {
Charset: 'UTF-8',
Data: `Your Adieu login code: ${secretLoginCode}
...or follow this link: ${process.env.LOGINREDIRECTURL + secretLoginCode}
`
}
},
Subject: {
Charset: 'UTF-8',
Data: 'Your Adieu login code'
}
},
Source: process.env.SESFROMADDRESS
}
await ses.sendEmail(params).promise()
}
exports.handler = async (event, context) => {
let secretLoginCode
if (!event.request.session || !event.request.session.length) {
// This is a new auth session
// Generate a new secret login code and mail it to the user
secretLoginCode = cryptoRandomString({length: 6, type: 'numeric'})
await sendEmail(event.request.userAttributes.email, secretLoginCode)
} else {
// There's an existing session. Don't generate new digits but
// re-use the code from the current session. This allows the user to
// make a mistake when keying in the code and to then retry, rather
// the needing to e-mail the user an all new code again.
const previousChallenge = event.request.session.slice(-1)[0]
secretLoginCode = previousChallenge.challengeMetadata.match(
/CODE-(\d*)/
)[1]
}
// This is sent back to the client app
event.response.publicChallengeParameters = {
email: event.request.userAttributes.email
}
// Add the secret login code to the private challenge parameters
// so it can be verified by the "Verify Auth Challenge Response" trigger
event.response.privateChallengeParameters = { secretLoginCode }
// Add the secret login code to the session so it is available
// in a next invocation of the "Create Auth Challenge" trigger
event.response.challengeMetadata = `CODE-${secretLoginCode}`
context.done(null, event)
}
exports.handler = (event, context) => {
if (
event.request.session &&
event.request.session.find(
(attempt) => attempt.challengeName !== 'CUSTOM_CHALLENGE'
)
) {
// We only accept custom challenges fail auth
event.response.issueTokens = false
event.response.failAuthentication = true
} else if (
event.request.session &&
event.request.session.length >= 3 &&
event.request.session.slice(-1)[0].challengeResult === false
) {
// The user provided a wrong answer 3 times fail auth
event.response.issueTokens = false
event.response.failAuthentication = true
} else if (
event.request.session &&
event.request.session.length &&
event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE' && // Doubly stitched, holds better
event.request.session.slice(-1)[0].challengeResult === true
) {
// The user provided the right answer succeed auth
event.response.issueTokens = true
event.response.failAuthentication = false
} else {
// The user did not provide a correct answer yet present challenge
event.response.issueTokens = false
event.response.failAuthentication = false
event.response.challengeName = 'CUSTOM_CHALLENGE'
}
context.done(null, event)
}
# NOT MEANT TO BE RUN
$ amplify init
# enter your project details, make sure you have an AWS credential named 'default' in ~/.aws/credentials
$ amplify add auth
# Do you want to use the default authentication and security configuration?
# Default configuration
# ❯ Default configuration with Social Provider (Federation)
# Manual configuration
# I want to learn more.
# Warning: you will not be able to edit these selections.
# How do you want users to be able to sign in?
# Username
# ❯ Email
# Phone Number
# Email or Phone Number
# I want to learn more.
# Do you want to configure advanced settings?
# No, I am done.
# ❯ Yes, I want to make some additional changes.
# Warning: you will not be able to edit these selections.
# What attributes are required for signing up? (Press <space> to select, <a> to toggle all, <i> to invert selection)
# ❯◯ Address (This attribute is not supported by Facebook, Google, Login With Amazon.)
# ◯ Birthdate (This attribute is not supported by Login With Amazon.)
# ◉ Email
# ◯ Family Name (This attribute is not supported by Login With Amazon.)
# ◯ Middle Name (This attribute is not supported by Google, Login With Amazon.)
# ◯ Gender (This attribute is not supported by Login With Amazon.)
# ◯ Locale (This attribute is not supported by Facebook, Google.)
# (Move up and down to reveal more choices)
# Do you want to enable any of the following capabilities?
# ◯ Add Google reCaptcha Challenge
# ◯ Email Verification Link with Redirect
# ◯ Add User to Group
# ◯ Email Domain Filtering (blacklist)
# ◯ Email Domain Filtering (whitelist)
# ❯◉ Custom Auth Challenge Flow (basic scaffolding - not for production) [THIS OPTION ALLOWS FOR LOGGING IN USING EMAIL & VERIFY CODE]
# ◯ Override ID Token Claims
# Set a domain prefix
# Set any OAuth redirect signin URIs
# Set any OAuth redirect signout URIs
# Select the social providers you want to configure for your user pool: (Press <space> to select, <a> to toggle all, <i> to invert selection)
# ❯◯ Facebook
# ◯ Google
# ◯ Login With Amazon
# Follow Social App setup instructions here: https://docs.amplify.aws/lib/auth/social/q/platform/js#setup-your-auth-provider
# Enter App IDs and secrets depending on the social options chosen
# [ONLY FOR CUSTOM AUTH FLOW] Prompted to set up Create, Define, and Verify Auth challenge lambda functions
$ amplify push
# sends the whole local config to AWS for diffing & build out
import { CognitoUserPoolTriggerHandler } from 'aws-lambda'
export const handler: CognitoUserPoolTriggerHandler = async (event) => {
event.response.autoConfirmUser = true
if (event.request.userAttributes.hasOwnProperty("email")) {
event.response.autoVerifyEmail = true
}
return event
}
exports.handler = (event, context) => {
const expectedAnswer =
event.request.privateChallengeParameters.secretLoginCode
if (event.request.challengeAnswer === expectedAnswer) {
event.response.answerCorrect = true
} else {
event.response.answerCorrect = false
}
context.done(null, event)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment