
Emmanuel Omonbude
May 25, 2026
Add Social Sign-In to Your Dash App
Author: Emmanuel Omonbude
If you've ever tried to add Google or GitHub login to a Plotly Dash app, you know it’s not a straightforward process. OAuth flows, PKCE, state parameters, token exchange, user info endpoints and every provider does things slightly differently. Most existing libraries are built for Django or generic Flask, not Dash.
That's exactly why I built dash-social-signin.
What is dash-social-signin?
dash-social-signin is a lightweight Python package that gives you everything you need to add social sign-in to a Dash app; frontend button assets, backend OAuth helpers, PKCE support, and step-by-step provider setup guides. All in one package, built specifically for Dash.
Supported providers:
● GitHub
● X (Twitter)
● Microsoft
● Apple
● Discord
● Slack
Why use it?
1. Built for Dash
Most OAuth libraries assume you're using Django or a generic Flask app. Dash has its own layout system and asset pipeline. This package works with that, not against it.
2. One consistent API for all providers
Every provider has quirks. Facebook uses a different token endpoint format. X requires PKCE. Apple requires a JWT as the client secret. This package smooths all of that over so you call the same functions regardless of which provider you're using.
3. Security built in
OAuth done wrong is a security hole. This package includes:
- PKCE (Proof Key for Code Exchange), which prevents authorization code interception attacks
- State parameter / CSRF protection, where one random token is generated per login attempt, stored server-side, and verified on callback. A mismatched state will result in a rejected request.
- Server-side token exchange, where tokens are never exposed to the browser. All exchange happens on your Flask/Dash backend
- Safe defaults. Ff DASH_SOCIAL_SIGNIN_SECRET is not set, the app warns you. Debug mode is disabled in production.
4. Lightweight
No heavy frontend frameworks required. The package ships with lightweight vanilla JS and CSS assets that render buttons automatically but using them is completely optional. If you prefer a fully custom design, you can skip the built-in styles entirely and wire your own UI directly to the backend helpers.
5. Flexible
Use the built-in build_container() for a ready-made UI, or skip it entirely and build your own buttons, the backend helpers work either way.
Installation
Before installing dash-social-signin, ensure you have dash and python-dotenv installed in your project environment.
pip install dash python-dotenv dash-social-signin dash-bootstrap-components
Quick Start Guide
This quick start pipeline is divided into three key sections, depending on your integration needs. Section A details implementing the pre-styled default UI buttons. Section B covers designing your own custom layout buttons utilizing built-in Font Awesome icons. And Section C sets up the mandatory backend route handlers required by both implementations (/auth/start and /auth/callback).
NOTE: Depending on your implementation, you should keep your client credentials safely inside a .env file. See the .env.example on how the env variables should look like. Also see the PROVIDERS.md on how to get them.
Section A: Using the Default Login Buttons
To use the default, pre-styled UI buttons provided out-of-the-box by the library, you must first copy the component static files to your app assets pipeline, then mount the setup details in your layout context:
Step 1: Install stock styles and JS
from dash_social_signin import install_assets
install_assets("./assets")
Step 2: Pull container frame into layout
import osfrom dash import Dash, htmlfrom dash_social_signin import build_containerfrom dotenv import load_dotenv# Load environment variables from your local .env fileload_dotenv()app = Dash(__name__)app.server.secret_key = os.environ.get("SECRET_KEY", "change-me-in-production")BASE_URL = os.environ.get("BASE_URL", "https://your-domain.com")app.layout = html.Div([build_container({"providers": {"google": {"clientId": os.environ.get("GOOGLE_CLIENT_ID"),"redirectUri": f"{BASE_URL}/auth/callback?provider=google","authUrl": f"{BASE_URL}/auth/start","extraParams": {"provider": "google"},"scope": "openid email profile",},"github": {"clientId": os.environ.get("GITHUB_CLIENT_ID"),"redirectUri": f"{BASE_URL}/auth/callback?provider=github","authUrl": f"{BASE_URL}/auth/start","extraParams": {"provider": "github"},"scope": "read:user user:email",},}},id="social-signin",)])
Section B: Building Custom Login Buttons
If you want full control over your application UI layout, you can skip install_assets() and build_container() completely. You are free to design your own custom HTML layout items or use library components like dash_bootstrap_components integrated alongside Font Awesome stylesheet links that are natively built into Dash. The only requirement is that your elements must anchor or link directly to the backend authorization pathway route tracking point initialized at /auth/start.
Here is an example setup demonstrating how to leverage Dash's built-in icon bundles to render a native Google brand icon button component linking right into your backend context handle point:
import osimport dashimport dash_bootstrap_components as dbcfrom dash import htmlfrom dotenv import load_dotenv# Load environment variables from your local .env fileload_dotenv()app = dash.Dash(__name__,external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME])app.server.secret_key = os.environ.get("SECRET_KEY", "change-me-in-production")BASE_URL = os.environ.get("BASE_URL", "https://your.app")app.layout = html.Div([html.H3("Sign In to Your Account"),html.A(dbc.Button([html.I(className="fab fa-google me-2"), # Font Awesome Icon"Continue with Google"],color="danger",outline=True,className="d-flex align-items-center px-4 py-2"),# Target your backend route with required parameters, appending the dynamic redirectUrihref=f"/auth/start?provider=google&scope=openidemailprofile&redirectUri={BASE_URL}/auth/callback?provider=google",style={"textDecoration": "none"})],className="d-flex flex-column align-items-center justify-content-center vh-100")
Section C: The Mandatory Backend Routes
Regardless of whether you choose the ready-made container (Section A) or a fully custom button layout (Section B), both implementations rely on the same mandatory server-side endpoints to
securely manage state tokens, verify PKCE parameters, and prevent authorization code interception.
1. /auth/start - Handshake initiation, state validation generation, and provider redirect routing:
from flask import redirect, request, sessionfrom dash_social_signin import build_authorize_url, build_pkce_verifier, build_pkce_challengeimport secretsimport os@app.server.route("/auth/start")def auth_start():provider = request.args.get("provider")# Dynamically extract the redirectUri passed from your frontend context redirect_uri = request.args.get("redirectUri") orf"{BASE_URL}/auth/callback?provider={provider}"state = secrets.token_urlsafe(16)session[f"oauth_state:{provider}"] = stateverifier = build_pkce_verifier()session[f"pkce_verifier:{provider}"] = verifierchallenge = build_pkce_challenge(verifier)auth_url = build_authorize_url(provider=provider,client_id=os.environ.get(f"{provider.upper()}_CLIENT_ID") or "YOUR_CLIENT_ID", redirect_uri=redirect_uri,scope=request.args.get("scope"),state=state,code_challenge=challenge,)return redirect(auth_url)
2. /auth/callback - Intercepts the authorization redirect, handles server-side token validation, and fetches user information:
from flask import jsonify, request, sessionfrom dash_social_signin import verify_oauth_callbackimport os@app.server.route("/auth/callback", methods=["GET", "POST"])def auth_callback():get_param = lambda k: request.args.get(k) or request.form.get(k)provider = get_param("provider")code = get_param("code")# CSRF checkreturned_state = get_param("state")expected_state = session.pop(f"oauth_state:{provider}", None)if expected_state and returned_state != expected_state:return "Invalid state", 400code_verifier = session.pop(f"pkce_verifier:{provider}", None)tokens, userinfo = verify_oauth_callback(provider=provider,code=code,redirect_uri=f"{BASE_URL}/auth/callback?provider={provider}",client_id=os.environ.get(f"{provider.upper()}_CLIENT_ID") or "YOUR_CLIENT_ID", client_secret=os.environ.get(f"{provider.upper()}_CLIENT_SECRET") or "YOUR_CLIENT_SECRET",code_verifier=code_verifier,)# At this point you have the user's info.# Save to your database, set a session cookie, redirect to your dashboard. return jsonify({"provider": provider, "userinfo": userinfo})
Running Your App
To run your production-ready Dash server locally or inside your container, make sure to include the entry point block at the very end of your main application script:
import osif __name__ == "__main__":port = int(os.environ.get("PORT", 8050))app.run(host="0.0.0.0", port=port, debug=True)
Security Features in Detail
PKCE
PKCE protects against authorization code interception. Before redirecting the user to the provider, a random verifier is generated and stored server-side. A derived challenge is sent to the provider. When the code comes back, the verifier is sent with the token request. The provider checks they match. An intercepted code is useless without the verifier.
verifier = build_pkce_verifier() # random secret
challenge = build_pkce_challenge(verifier) # SHA-256 hash of verifier
CSRF protection via state
Before every login attempt, a random state token is generated and stored in the server session. The provider echoes it back in the callback. If the returned state doesn't match what was stored, the request is rejected. This prevents cross-site request forgery attacks.
Tokens stay on the server
The browser never sees your client secret or access tokens. Everything is exchanged server-side. In a production app, you'd store the tokens in your database and give the user a session cookie. Never return raw tokens to the frontend.
Deployment
Set these environment variables before deploying: Check the .env.examplefor more details
BASE_URL=https://your-domain.comDASH_SOCIAL_SIGNIN_SECRET=a-long-random-stringGOOGLE_CLIENT_ID=...GOOGLE_CLIENT_SECRET=...# etc.
Make sure your production URL is registered as a valid redirect URI in each provider's developer console. The pattern is always:
https://your-domain.com/auth/callback?provider=PROVIDER
Here is the list of providers (valid values for the ?provider= parameter):
● github
● x
● microsoft
● apple
● discord
● slack
Full step-by-step setup guides for all 9 providers are in PROVIDERS.md.
What this Package Does NOT Do
It handles OAuth. What you do after that (saving users to a database, managing sessions, handling logout, registration flows) is up to you. This is intentional. Every app has different requirements, and this package shouldn't force a database or session library on you.
After verify_oauth_callback() returns user info, you have the user's name, email, and profile picture. The rest is your app's business logic.
Check out the GitHub repo and PyPI for more details:
- GitHub: https://github.com/budescode/dash-social-signin
- PyPI: https://pypi.org/project/dash-social-signin
If you have any questions, run into any bugs, or have feature requests, please open an issue in the repo.