User Sign In
Description of the current user sign-in flow for webar and scraper
All code for managing user auth is stored in the @shared/auth context.
Any file inside this directory may import freely from other files in the directory. Files outside of shared/auth
should only import from the top-level @shared/auth
file.
Exported functions
The useful auth functions should be exported from @shared/auth.
They are:
UserProfileButton
This component renders a button that opens the login modal if the user is signed out, and the user profile button if they are signed in. In general, this is the only component you need to worry about when adding a path for the user to log in.
useAuthWatch
This hook should be rendered in one place for every application. It sets up a Firebase watch to keep track of the current Firebase user, and load their user profile information into state. It also clears the user data when the user logs out of Firebase.
useInitializeAddressOverride
This hook should be rendered in one place for every application. It pulls an address override from localstorage.getItem("illust:addressOverride")
, and uses that to override the current logged-in address. This override does not let you publish, but does let you view data as another user.
User state hooks:
useCurrentUser
useAddress
useIsLoadingAuth
useIsLoggedIn
These hooks let you get the current state of the logged-in user
authHeader
This function returns the header for an authorized request to the API
User auth flow
UserProfileButton
renders LoginForm
, which presents a series of buttons for a user to use to log in. Each button is associated with a different Wallet provider (Metamask, Wallet Connect, Magic Links). It also calls useLogin
, which returns a login
function that can be passed a provider name.
When the user clicks a login button, login
uses the isLoggingIn
store to set up a nonce for that login attempt. If the user clicks "Cancel" during the login, the nonce is incremented. The login promise flow periodically checks if its nonce is the current one. If it is not, it ends the login attempt and resets the login state, so the user can try logging in again.
Once the login attempt is set up, login
calls getLoginCredentials
, which calls a getCredentails
function for the chosen provider. This function should return an ethers.js WalletProvider
that is used to sign a message, and any user data returned from the provider like an email or username. It also returns a disconnect
method to call after login is complete, so the wallet does not stay connected, since it is no longer needed.
login
then fetches a nonce token from the API, which may only be used once, and prevents replay attacks with a signature.
The WalletProvider
, token, and other credentials are then passed to loginWithFirebase
, which asks the user to sign a message (that includes the nonce token) with their provider. That signature is passed to the /users/login
API route, which either finds or creates a user for the address the signed the message. For new users, the address used to sign the message is stored as userProfile.addresses.eth[0]
. The API then returns a Firebase Custom Token. It also sets a session
cookie that the client can use to sign itself in with on other subdomains.
On the client, Firebase uses the custom token to sign in. If successful, this triggers useCurrentUser
to note that the user is signed in, and fetch their user profile. Once complete, hooks like useCurrentUser
will return the newly signed-in user.
Finally, the returned disconnect
method for the wallet gets called, closing the connection to the wallet. At this point, the application no longer needs wallet signatures, and instead relies on Firebase to prove the user owns a wallet.
To make authorized requests to the API, the client uses authHeader
to add a firebase token to the request header, and the API uses verifyAddressFromAuthToken
to verify the header is valid, and the user that sent the header has access based on their address.
iuseAuthWatchuseAuthWatchuseAuthWatchuseAuthWatchuseAuthWatchuseAuthWatc
Last updated