Last active
April 6, 2021 03:58
-
-
Save hrafnkellpalsson/92657f1253432595d7800dd58bc8b507 to your computer and use it in GitHub Desktop.
Auth state machine for AWS Amplify Authenticator component
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Machine } from "xstate" | |
| import Auth from "@aws-amplify/auth" | |
| import { Hub } from "@aws-amplify/core" | |
| import { sentry } from "../sentry" | |
| import { queryCache } from "react-query" | |
| // Some of the events from the Hub auth channel are in the documentation here | |
| // https://docs.amplify.aws/lib/auth/auth-events/q/platform/js | |
| // Even more events in the source code | |
| // https://github.com/aws-amplify/amplify-js/blob/92d8d800256119d1ba84bb90097f91a983b1e5c0/packages/auth/src/Auth.ts | |
| export const AuthEvents = { | |
| configured: "configured", | |
| tokenRefresh: "tokenRefresh", | |
| tokenRefresh_failure: "tokenRefresh_failure", | |
| signIn: "signIn", | |
| signIn_failure: "signIn_failure", | |
| signUp: "signUp", | |
| signUp_failure: "signUp_failure", | |
| signOut: "signOut", | |
| forgotPassword: "forgotPassword", | |
| forgotPassword_failure: "forgotPassword_failure", | |
| forgotPasswordSubmit: "forgotPasswordSubmit", | |
| forgotPasswordSubmit_failure: "forgotPasswordSubmit_failure", | |
| completeNewPassword_failure: "completeNewPassword_failure", | |
| } | |
| export const authMachine = Machine( | |
| { | |
| id: "auth", | |
| strict: false, // The Hub sends a number of auth events we ignore | |
| initial: "checkingIfLoggedIn", | |
| invoke: { | |
| src: "listenToAuthEvents", | |
| }, | |
| states: { | |
| checkingIfLoggedIn: { | |
| invoke: { | |
| src: "checkIfLoggedIn", | |
| }, | |
| on: { | |
| [AuthEvents.signIn]: "loggedIn", | |
| [AuthEvents.signOut]: "loggedOut", | |
| }, | |
| }, | |
| loggedIn: { | |
| on: { | |
| [AuthEvents.signOut]: "loggedOut", | |
| }, | |
| }, | |
| loggedOut: { | |
| entry: ["clearRQCache", "clearSentryScope"], | |
| invoke: { | |
| src: "logOutCleanup", | |
| // We do an internal self transition so the service won't be invoked again. Otherwise we'd get into an endless loop. | |
| onError: { target: "loggedOut", internal: true }, | |
| }, | |
| on: { | |
| [AuthEvents.signIn]: "loggedIn", | |
| [AuthEvents.signUp_failure]: "signUpFailed", | |
| }, | |
| }, | |
| signUpFailed: { | |
| on: { | |
| [AuthEvents.signIn]: "loggedIn", | |
| }, | |
| }, | |
| }, | |
| }, | |
| { | |
| services: { | |
| checkIfLoggedIn: (_ctx, _e) => async (sendParent) => { | |
| try { | |
| // This method can be used to check if a user is logged in when the page is loaded. | |
| // It will throw an error if there is no user logged in. | |
| // https://docs.amplify.aws/lib/auth/manageusers/q/platform/js#retrieve-current-authenticated-user | |
| const _user = await Auth.currentAuthenticatedUser() | |
| sendParent(AuthEvents.signIn) | |
| } catch (e) { | |
| // The Amplify Auth package throws a string rather than an error | |
| // For extra safety we could log to a service if we get a different error | |
| const expectedError = "The user is not authenticated" | |
| sendParent(AuthEvents.signOut) | |
| } | |
| }, | |
| listenToAuthEvents: (_ctx, _e) => (sendParent) => { | |
| // Note that after user confirms their sign up with the code they received in their email, | |
| // the Hub simply sends a 'signIn' event, there is no separate 'confirmSignUp' event. | |
| const hubListener = (data) => { | |
| const event = data.payload.event | |
| sendParent(event) | |
| } | |
| Hub.listen("auth", hubListener) | |
| // If any of the Cognito tokens, or any Cognito data, in local storage are deleted log we want to log the user out. | |
| // Note that storage events don't always fire on the page that modified storage, see | |
| // https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#responding_to_storage_changes_with_the_storageevent | |
| // Not sure if that applies only to programmatic changes to storage or manual changes also. | |
| // However, manual deletions of Cognito tokens from storage, do cause a storage event to fire in Chrome and Safari, and so the user | |
| // is logged out. This doesn't work in Firefox however, but whatever, if it works in other browsers that's good enough for me. | |
| const storageListener = (e) => { | |
| const { key, newValue } = e | |
| const isCognitoKey = key.includes("CognitoIdentityServiceProvider") | |
| const wasDeleted = newValue === null | |
| if (isCognitoKey && wasDeleted) { | |
| sendParent(AuthEvents.signOut) | |
| } | |
| } | |
| window.addEventListener("storage", storageListener) | |
| return () => { | |
| Hub.remove("auth", hubListener) | |
| window.removeEventListener("storage", storageListener) | |
| } | |
| }, | |
| logOutCleanup: (_ctx, _e) => async () => { | |
| // Clears tokens from local storage and sends sign out event to Hub auth channel | |
| await Auth.signOut() | |
| }, | |
| }, | |
| actions: { | |
| // We don't create a redirect action because the signOut event is only ever sent when clicking a link that already redirects to "/" | |
| // and we've set up our routing so whenever you navigate to any route apart from "/auth" when logged out you'll be redirected to "/" | |
| clearRQCache: (_ctx, _e) => queryCache.clear(), | |
| clearSentryScope: (_ctx, _e) => | |
| sentry.configureScope((scope) => scope.setUser(null)), | |
| }, | |
| } | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment