Integrating Authentiction into your Web Applications#

Freva supports OAuth2-based authentication using the Authorization Code Flow.

You can use the authentication endpoints of Freva’s RestAPI to secure your web applications with OpenID Connect (OIDC) authentication flow and creating OAuth2 tokens.

Authentication Flow#

The standard OIDC Authorization Code flow involves three steps:

1. Redirect to Login#

Redirect the user to the login endpoint with the redirect_uri parameter.

GET /api/freva-nextgen/auth/v2/login HTTP/1.1
host: www.freva.dkrz.de

{
     redirect_uri=http://localhost:8050/callback
}

2. Authorization Callback#

After user login, the identity provider redirects to your specified redirect_uri with the following query parameters:

  • code: a temporary authorization code

  • state: an opaque anti-CSRF token

You must exchange this code value with the Freva /token endpoint.

POST /api/freva-nextgen/auth/v2/token HTTP/1.1
host: www.freva.dkrz.de
Content-Type: application/x-www-form-urlencoded

{
     code=xyz122&redirect_uri=http://localhost:8050/callback&grant_type=authorization_code
}

This returns an OAuth2 access token and optional refresh token.

3. Retrieve User Info (optional):#

Use the gnerated access token to fetch the user’s identity:

GET /api/freva-nextgen/auth/v2/userinfo HTTP/1.1
host: www.freva.dkrz.de
Authorization: Bearer access_token

Alternatively, you can use the /systemuser endpoint to retrieve more detailed i information about the authenticated user. This endpoint is only accessible to primary (non-guest) users, making it useful for enforcing access restrictions or authorization in your application. By calling /systemuser with a valid access token, you can reliably verify whether the current user is a full (primary) account holder.

GET /api/freva-nextgen/auth/v2/systemuser HTTP/1.1
host: www.freva.dkrz.de
Authorization: Bearer access_token

Dash Example#

The following Plotly Dash app demonstrates how to integrate this authentication flow using Flask sessions to store tokens. It uses requests for API interaction and displays a simple authenticated Plotly graph.

  1import os
  2from typing import Any, Dict, Optional
  3
  4import dash
  5import plotly.express as px
  6import requests
  7from dash import Dash, Input, Output, dcc, html
  8from flask import Flask, Response, redirect, request, session
  9
 10# =============================
 11# CONFIGURATION
 12# =============================
 13
 14FREVA_API_BASE: str = "https://freva.dkrz.de/api/freva-nextgen/auth/v2"
 15REDIRECT_URI: str = "http://localhost:8050/callback"
 16LOGIN_ENDPOINT: str = f"{FREVA_API_BASE}/login"
 17TOKEN_ENDPOINT: str = f"{FREVA_API_BASE}/token"
 18USERINFO_ENDPOINT: str = f"{FREVA_API_BASE}/userinfo"
 19
 20# Initialize Flask server for Dash backend
 21server: Flask = Flask(__name__)
 22
 23# ⚠️ SECURITY WARNING:
 24# This key signs the session cookie. In production, use a secure env var:
 25# e.g., server.secret_key = os.environ["FLASK_SECRET_KEY"]
 26server.secret_key = os.urandom(24)
 27
 28# =============================
 29# DASH APP INITIALIZATION
 30# =============================
 31
 32app: Dash = dash.Dash(
 33    __name__, server=server, use_pages=False, suppress_callback_exceptions=True
 34)
 35app.title = "Freva OIDC Dash Example"
 36
 37# =============================
 38# FLASK AUTH ROUTES
 39# =============================
 40
 41
 42@server.route("/login")
 43def login() -> Response:
 44    """Redirects user to Freva's OIDC login page."""
 45    return redirect(f"{LOGIN_ENDPOINT}?redirect_uri={REDIRECT_URI}")
 46
 47
 48@server.route("/callback")
 49def callback() -> Response:
 50    """Handles OIDC redirect, exchanges code for token, and stores in session."""
 51    code: Optional[str] = request.args.get("code")
 52    state: Optional[str] = request.args.get("state")
 53
 54    if not code or not state:
 55        return Response("Missing code or state", status=400)
 56
 57    try:
 58        # Exchange authorization code for token
 59        token_res = requests.post(
 60            TOKEN_ENDPOINT,
 61            data={
 62                "code": code,
 63                "redirect_uri": REDIRECT_URI,
 64                "grant_type": "authorization_code",
 65            },
 66        )
 67        token_res.raise_for_status()
 68        tokens: Dict[str, Any] = token_res.json()
 69        session["access_token"] = tokens["access_token"]
 70
 71        # Retrieve user info
 72        userinfo_res = requests.get(
 73            USERINFO_ENDPOINT,
 74            headers={"Authorization": f"Bearer {tokens['access_token']}"},
 75        )
 76        userinfo_res.raise_for_status()
 77        session["userinfo"] = userinfo_res.json()
 78
 79    except requests.RequestException as e:
 80        return Response(f"Login failed: {str(e)}", status=500)
 81
 82    return redirect("/")
 83
 84
 85@server.route("/logout")
 86def logout() -> Response:
 87    """Clears the session and logs user out."""
 88    session.clear()
 89    return redirect("/")
 90
 91
 92# =============================
 93# DASH LAYOUT AND CALLBACK
 94# =============================
 95
 96app.layout = html.Div([dcc.Location(id="url", refresh=True), html.Div(id="main")])
 97
 98
 99@app.callback(Output("main", "children"), Input("url", "pathname"))
100def render_content(_: str) -> Any:
101    """Main page content: authenticated view or redirect to login."""
102    if "userinfo" not in session:
103        return dcc.Location(href="/login", id="redirect")
104
105    user: str = session["userinfo"].get("username", "Unknown")
106    access_token: str = session["access_token"]
107
108    fig = px.scatter(x=[1, 2, 3], y=[3, 1, 6], title="Freva Demo Scatter")
109
110    return html.Div(
111        [
112            html.H2(f"Welcome, {user}"),
113            html.P("Your OAuth2 token (access_token) is:"),
114            html.Pre(
115                access_token,
116                style={"whiteSpace": "pre-wrap", "wordBreak": "break-all"},
117            ),
118            dcc.Graph(figure=fig),
119            html.Br(),
120            html.A("Logout", href="/logout"),
121        ]
122    )
123
124
125# =============================
126# LOCAL ENTRY POINT (FOR DEBUG)
127# =============================
128
129if __name__ == "__main__":
130    # Use `gunicorn main:server` for production deployment
131    app.run(debug=True)

You can download the example file here: dash_auth_example.py.

Security Notes

  • Redirect URI: Must match the URI registered with the identity provider.

    Contact your freindly freva admins for advice.

  • Session Secret: Set server.secret_key using a secure environment variable in production.

  • HTTPS: Always use HTTPS in production to protect token integrity.

  • Use a WSGI-compatible server like Gunicorn for deployment:

    gunicorn dash_auth_example:server --bind 0.0.0.0:8050