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.
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With