Introducing Dash MCP + Building Dash Apps with Coding Agents. Reserve your spot.

blog icon darkBlog_icon_purple

Blog

doc_icon_darkDoc icon purple

Docs

login_icon_darkLogin icon purple

Log In

author photo

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: 

● Google 

● Facebook 

● GitHub 

● X (Twitter) 

● LinkedIn 

● 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 os
from dash import Dash, html
from dash_social_signin import build_container
from dotenv import load_dotenv
# Load environment variables from your local .env file
load_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 os
import dash
import dash_bootstrap_components as dbc
from dash import html
from dotenv import load_dotenv
# Load environment variables from your local .env file
load_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 redirectUri
            href=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, session
from dash_social_signin import build_authorize_url, build_pkce_verifier, build_pkce_challenge
import secrets
import 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") or
    f"{BASE_URL}/auth/callback?provider={provider}"
    state = secrets.token_urlsafe(16)
    session[f"oauth_state:{provider}"] = state
    verifier = build_pkce_verifier()
    session[f"pkce_verifier:{provider}"] = verifier
    challenge = 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, session
from dash_social_signin import verify_oauth_callback
import 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 check
    returned_state = get_param("state")
    expected_state = session.pop(f"oauth_state:{provider}", None)
    if expected_state and returned_state != expected_state:
        return "Invalid state", 400
   
    code_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 os
if __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.com 
DASH_SOCIAL_SIGNIN_SECRET=a-long-random-string 
GOOGLE_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): 

● google 

● facebook 

● github 

● x 

● linkedin 

● 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:

If you have any questions, run into any bugs, or have feature requests, please open an issue in the repo.

Bluesky icon
X icon
Instagram icon
Youtube icon
Medium icon
Facebook icon

Product

© 2026
Plotly. All rights reserved.
Cookie Preferences