How to wrap a third-party auth component and integrate it into a Reflex app.
Masen Furer
·
Almost any non-trivial web app needs a way to identify and authenticate users,
but Reflex does not provide this functionality out of the box because there are
way too many different ways to approach the problem. Thanks to the plethora of
existing React components for performing auth, a wrapper can be created to
include most third-party auth solutions within a Reflex app.
In this post, I'll walk through how I set up a Google API project and wrapped
@react-oauth/google to provide Sign In with Google functionality in my Reflex
app.
The @react-oauth/google
package provides a React component that handles all of the interaction with
Google's OAuth API. It has rich functionality and many options, but for the
purposes of this post, we will only wrap the props needed to get basic login
working.
The GoogleLogin component renders the familiar "Sign in with Google" button.
Since we will use the default configuration, no props are needed, however the
event trigger does need to be defined so our Reflex app is able to get the token
after logging in.
Define the following wrapper in the same react_oauth_google.py.
The on_success trigger is defined to pass its argument directly to the Reflex
event handler.
An event handler will be used to receive the token after a successful login.
Critically, the token must be verified and decoded to access the user
information it contains.
The simplest way to verify the token is to use Google's own google-auth python
library. Add google-auth[requests] to your app's requirements.txt and install it with
pip install -r requirements.txt.
Add the necessary imports to the module where your app State is defined and
set the CLIENT_ID saved earlier, as it is needed to verify the token.
The on_success trigger is fired by GoogleLogin after a successful login, and it contains the id_token that provides user information. For now, this event handler will verify the token and dump its contents to the console to verify that it is working.
With this minimal functionality in place, it should be possible to log in with
one of the test accounts defined earlier on the Consent Screen configuration.
Add the GoogleOAuthProvider and GoogleLogin components linked with the
previously defined CLIENT_ID and State.on_success event handler to test the
functionality so far.
After a successful login, you will see the decoded JSON Web Token
(JWT) with user profile information displayed in the terminal!
The GoogleLogin component does NOT store the token in any way, so it is up to
our app to store and manage the credential after login. For this purpose, we
will use an
rx.LocalStorageVar in the
State that is set in the
on_success event handler.
Additionally, an rx.var will be used to verify and return the decoded
token info for the frontend to use.
Finally, a new logout event handler will be defined to clear out the saved token
and effectively log the user out of the app.
For convenience, a token_is_valid computed var can be defined to return a
simple bool if the token is valid or not. This is specifically not a
cached var because it should be re-evaluated every time it is accessed, in
case the expiry time has passed.
Now that the user's token is stored in the state, its absence can be used to
prompt for login on protected pages. For this purpose, we will define a
decorator that can be applied to any page function which shows a login button if
the user token is not valid.
First define the login component that will be shown to unauthenticated users.
Then define the decorator that will wrap page components.
The production code for this example has been published as a reuable third-party
component, reflex-google-auth
and can be used directly in any Reflex app.