Auth¶
To ensure that the users of your Mesop application are authenticated, this guide provides a detailed, step-by-step process on how to integrate Firebase Authentication with Mesop using a web component.
Mesop is designed to be auth provider agnostic, allowing you to integrate any auth library you prefer, whether it's on the client-side (JavaScript) or server-side (Python). You can support sign-ins, including social sign-ins like Google's or any others that you prefer. The general approach involves signing in on the client-side first, then transmitting an auth token to the server-side.
Firebase Authentication¶
This guide will walk you through the process of integrating Firebase Authentication with Mesop using a custom web component.
Pre-requisites: You will need to create a Firebase account and project. It's free to get started with Firebase and use Firebase auth for small projects, but refer to the pricing page for the most up-to-date information.
We will be using three libraries from Firebase to build an end-to-end auth flow:
- Firebase Web SDK: Allows you to call Firebase services from your client-side JavaScript code.
- FirebaseUI Web: Provides a simple, customizable auth UI integrated with the Firebase Web SDK.
- Firebase Admin SDK (Python): Provides server-side libraries to integrate Firebase services, including Authentication, into your Python applications.
Let's dive into how we will use each one in our Mesop app.
Web component¶
The Firebase Authentication web component is a custom component built for handling the user authentication process. It's implemented using Lit, a simple library for building lightweight web components.
JS code¶
import {
LitElement,
html,
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';
import 'https://www.gstatic.com/firebasejs/10.0.0/firebase-app-compat.js';
import 'https://www.gstatic.com/firebasejs/10.0.0/firebase-auth-compat.js';
import 'https://www.gstatic.com/firebasejs/ui/6.1.0/firebase-ui-auth.js';
// TODO: replace this with your web app's Firebase configuration
const firebaseConfig = {
apiKey: 'AIzaSyAQR9T7sk1lElXTEUBYHx7jv7d_Bs2zt-s',
authDomain: 'mesop-auth-test.firebaseapp.com',
projectId: 'mesop-auth-test',
storageBucket: 'mesop-auth-test.appspot.com',
messagingSenderId: '565166920272',
appId: '1:565166920272:web:4275481621d8e5ba91b755',
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const uiConfig = {
// TODO: change this to your Mesop page path.
signInSuccessUrl: '/web_component/firebase_auth/firebase_auth_app',
signInFlow: 'popup',
signInOptions: [firebase.auth.GoogleAuthProvider.PROVIDER_ID],
// tosUrl and privacyPolicyUrl accept either url string or a callback
// function.
// Terms of service url/callback.
tosUrl: '<your-tos-url>',
// Privacy policy url/callback.
privacyPolicyUrl: () => {
window.location.assign('<your-privacy-policy-url>');
},
};
// Initialize the FirebaseUI Widget using Firebase.
const ui = new firebaseui.auth.AuthUI(firebase.auth());
class FirebaseAuthComponent extends LitElement {
static properties = {
isSignedIn: {type: Boolean},
authChanged: {type: String},
};
constructor() {
super();
this.isSignedIn = false;
}
createRenderRoot() {
// Render in light DOM so firebase-ui-auth works.
return this;
}
firstUpdated() {
firebase.auth().onAuthStateChanged(
async (user) => {
if (user) {
this.isSignedIn = true;
const token = await user.getIdToken();
this.dispatchEvent(new MesopEvent(this.authChanged, token));
} else {
this.isSignedIn = false;
this.dispatchEvent(new MesopEvent(this.authChanged, ''));
}
},
(error) => {
console.log(error);
},
);
ui.start('#firebaseui-auth-container', uiConfig);
}
signOut() {
firebase.auth().signOut();
}
render() {
return html`
<div
id="firebaseui-auth-container"
style="${this.isSignedIn ? 'display: none' : ''}"
></div>
<div
class="firebaseui-container firebaseui-page-provider-sign-in firebaseui-id-page-provider-sign-in firebaseui-use-spinner"
style="${this.isSignedIn ? '' : 'display: none'}"
>
<button
style="background-color:#ffffff"
class="firebaseui-idp-button mdl-button mdl-js-button mdl-button--raised firebaseui-idp-google firebaseui-id-idp-button"
@click="${this.signOut}"
>
<span class="firebaseui-idp-text firebaseui-idp-text-long"
>Sign out</span
>
</button>
</div>
`;
}
}
customElements.define('firebase-auth-component', FirebaseAuthComponent);
What you need to do:
- Replace
firebaseConfig
with your Firebase project's config. Read the Firebase docs to learn how to get yours. - Replace the URLs
signInSuccessUrl
with your Mesop page path andtosUrl
andprivacyPolicyUrl
to your terms and services and privacy policy page respectively.
How it works:
- This creates a simple and configurable auth UI using FirebaseUI Web.
- Once the user has signed in, then a sign out button is shown.
- Whenever the user signs in or out, the web component dispatches an event to the Mesop server with the auth token, or absence of it.
- See our web component docs for more details.
Python code¶
from typing import Any, Callable
import mesop.labs as mel
@mel.web_component(path="./firebase_auth_component.js")
def firebase_auth_component(on_auth_changed: Callable[[mel.WebEvent], Any]):
return mel.insert_web_component(
name="firebase-auth-component",
events={
"authChanged": on_auth_changed,
},
)
How it works:
- Implements the Python side of the Mesop web component. See our web component docs for more details.
Integrating into the app¶
Let's put it all together:
import firebase_admin
from firebase_admin import auth
import mesop as me
import mesop.labs as mel
from mesop.examples.web_component.firebase_auth.firebase_auth_component import (
firebase_auth_component,
)
# Avoid re-initializing firebase app (useful for avoiding warning message because of hot reloads).
if firebase_admin._DEFAULT_APP_NAME not in firebase_admin._apps:
default_app = firebase_admin.initialize_app()
@me.page(
path="/web_component/firebase_auth/firebase_auth_app",
stylesheets=[
"https://www.gstatic.com/firebasejs/ui/6.1.0/firebase-ui-auth.css"
],
# Loosen the security policy so the firebase JS libraries work.
security_policy=me.SecurityPolicy(
dangerously_disable_trusted_types=True,
allowed_connect_srcs=["*.googleapis.com"],
allowed_script_srcs=[
"*.google.com",
"https://www.gstatic.com",
"https://cdn.jsdelivr.net",
],
),
)
def page():
email = me.state(State).email
if email:
me.text("Signed in email: " + email)
else:
me.text("Not signed in")
firebase_auth_component(on_auth_changed=on_auth_changed)
@me.stateclass
class State:
email: str
def on_auth_changed(e: mel.WebEvent):
firebaseAuthToken = e.value
if not firebaseAuthToken:
me.state(State).email = ""
return
decoded_token = auth.verify_id_token(firebaseAuthToken)
# You can do an allowlist if needed.
# if decoded_token["email"] != "allowlisted.user@gmail.com":
# raise me.MesopUserException("Invalid user: " + decoded_token["email"])
me.state(State).email = decoded_token["email"]
Note You must add firebase-admin
to your Mesop app's requirements.txt
file
How it works:
- The
firebase_auth_app.py
module integrates the Firebase Auth web component into the Mesop app. It initializes the Firebase app, defines the page where the Firebase Auth web component will be used, and sets up the state to store the user's email. - The
on_auth_changed
function is triggered whenever the user's authentication state changes. If the user is signed in, it verifies the user's ID token and stores the user's email in the state. If the user is not signed in, it clears the email from the state.
Next steps¶
Congrats! You've now built an authenticated app with Mesop from start to finish. Read the Firebase Auth docs to learn how to configure additional sign-in options and much more.