Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to persist CognitoUser during signIn with CUSTOM_AUTH authentication flow

My React Native app uses Amplify for a CUSTOM_AUTH authentication flow. The user receives a link via email to satisfy a challengeAnswer request. The process is like this:

User initiatiates sign in:

const cognitoUser = await Auth.signIn(username);

Email is sent to user via lambda.

User leaves app to retrieve email.

User clicks a link in the email which routes user back to the app via the RN Linking api.

The code from the link is processed with:

await Auth.sendCustomChallengeAnswer(
  cognitoUser,
  authChallengeAnswer
);

Usually this works well, but there is no guarantee that the cognitoUser object will exist after the app has been backgrounded while the user retrieves the email. There is a non-zero chance that iOS could dump the app during this time, and the cognitoUser var would be gone forcing the user to restart the sign in process. I'm looking for a way to persist the cognitoUser object somehow so if iOS decides the app needs to die this var can be retrieved from cache.

I'm able to cache the object into the Amplify cache (AsyncStorage) with

await Cache.setItem("cognitoUser", cognitoUser);

then fetch with

await Cache.getItem("cognitoUser");

which fails with

TypeError: user.sendCustomChallengeAnswer is not a function

because the process of caching it lost all its __proto__ functions. Its just retrieved as a basic object.

I suspect the cause is that I'm not using TypeScript, and the object loses some type information somehow.

Is there a better way of persisting this CognitoUser object so I can guarantee it exists after the user leaves/returns to the app as is needed in a CUSTOM_AUTH flow.

like image 869
Fook Avatar asked Feb 24 '19 00:02

Fook


1 Answers

I use the following code to persist CognitoUser during sign in with CUSTOM_AUTH authentication flow:

import Auth from '@aws-amplify/auth'
import { CognitoUser } from 'amazon-cognito-identity-js'

const CUSTOM_AUTH_TTL = 5 * 60 * 1000 // Milliseconds

interface CustomAuthSession {
  username: string
  session: string
  // Milliseconds after epoch
  expiresAt: number
}

function clearCustomAuthSession() {
  window.localStorage.removeItem('CustomAuthSession')
}

function loadCustomAuthSession(): CognitoUser {
  const raw = window.localStorage.getItem('CustomAuthSession')
  if (!raw) {
    throw new Error('No custom auth session')
  }
  const storedSession: CustomAuthSession = window.JSON.parse(raw)
  if (storedSession.expiresAt < window.Date.now()) {
    clearCustomAuthSession()
    throw new Error('Stored custom auth session has expired')
  }
  const username = storedSession.username
  // Accessing private method of Auth here which is BAD, but it's still the
  // safest way to restore the custom auth session from local storage, as there
  // is no interface that lets us do it.
  // (If we created a new user pool object here instead to pass to a
  // CognitoUser constructor that would likely result in hard to catch bugs,
  // as Auth can assume that all CognitoUsers passed to it come from its pool
  // object.)
  const user: CognitoUser = (Auth as any).createCognitoUser(username)
  // Session is not exposed to TypeScript, but it's a public member in the
  // JS code.
  ;(user as any).Session = storedSession.session
  return user
}

function storeCustomAuthSession(cognitoUser: CognitoUser) {
  // Session isn't exposed to TypeScript, but it's a public member in JS
  const session = (cognitoUser as any).Session
  const expiresAt = window.Date.now() + CUSTOM_AUTH_TTL
  const otpSession: CustomAuthSession = {
    session,
    expiresAt,
    username: cognitoUser.getUsername(),
  }
  const json = window.JSON.stringify(otpSession)
  window.localStorage.setItem('CustomAuthSession', json)
}
like image 120
Agost Biro Avatar answered Oct 20 '22 01:10

Agost Biro



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!