UseThatApp Logo
    Python/Django

    Django

    Sign users in with their UseThatApp account and read their live plan in a Django app, over OAuth 2.0 / OpenID Connect. These views wire your app into the whole selling loop: a buyer who purchases from your own website lands back here signed in, entitlement already live — and marketplace launches arrive the same way. The code on this page is taken directly from the tested examples/django_min/ in the Python SDK. For the conceptual model, see OpenID Connect.

    This guide covers the Hosted sign-in path

    It has UseThatApp run login, so buyers return signed in and the same session answers what they bought — the Hosted sign-in add-on. If your app already has its own accounts, you do not need any of the sign-in wiring below: keep your login and verify purchases with a license key your server validates in one call, included in the per-sale fee.

    How it works

    Three views plus a gated home view, wired into your URL conf. The usethatapp SDK runs on your server as a confidential client and reads its config from django.conf.settings when Django is installed (falling back to environment variables otherwise).

    1. login — begin_login() returns an authorization URL and a JSON-able flow_state; stash it in request.session and redirect.
    2. callback — check request.GET["error"] first, then complete_login() returns a UtaSession.
    3. logout — redirect to logout_url(...) for RP-initiated sign-out.
    4. Gated view — call get_entitlement(access_token) for the live plan.

    Prerequisites

    • Python 3.9+ and a Django project.
    • The session middleware enabled (django.contrib.sessions.middleware.SessionMiddleware) — flow_state and the tokens live in the session.
    • UTA_CLIENT_ID, UTA_CLIENT_SECRET, and UTA_REDIRECT_URI available — set them in your environment or add them to settings.py. Register the OAuth client and redirect URI on your app's Integration page on the UseThatApp dashboard.

    Step 1 — Install

    terminal
    pip install usethatapp django

    Step 2 — Configure

    environment
    export UTA_CLIENT_ID=...
    export UTA_CLIENT_SECRET=...                 # server-side only
    export UTA_REDIRECT_URI=http://localhost:8000/callback/
    # Pointing at a non-production instance? Set BOTH of these
    # (each defaults to production):
    # export UTA_ISSUER=http://localhost:8000/o
    # export UTA_API_URL=http://localhost:8000

    Step 3 — Views

    Read the callback query params from request.GET, store flow_state in request.session, and issue redirects. Persist session.sub as your local user key.

    views.py
    from django.http import HttpResponse, JsonResponse
    from django.shortcuts import redirect
    
    from usethatapp import (
        UtaError,
        UtaTokenError,
        begin_login,
        complete_login,
        get_entitlement,
        logout_url,
    )
    
    
    def login(request):
        auth_url, flow_state = begin_login()
        request.session["uta_flow"] = flow_state          # stash for the callback
        return redirect(auth_url)
    
    
    def callback(request):
        # On cancel/deny, OAuth redirects back with ?error=... and no code.
        if request.GET.get("error"):
            request.session.pop("uta_flow", None)
            return redirect("home")
        try:
            session = complete_login(
                code=request.GET.get("code"),
                state=request.GET.get("state"),
                flow_state=request.session.pop("uta_flow", {}),
            )
        except UtaError as e:
            return HttpResponse(f"login failed: {e}", status=400)
        # Persist what you need against your own session.
        request.session["uta_sub"] = session.sub
        request.session["uta_access_token"] = session.access_token
        request.session["uta_id_token"] = session.id_token
        return redirect("home")
    
    
    def home(request):
        token = request.session.get("uta_access_token")
        if token:
            try:
                ent = get_entitlement(token)
                return JsonResponse({"sub": request.session.get("uta_sub"), "entitlement": ent.raw})
            except UtaTokenError:
                # Token revoked/expired (e.g. signed out of UseThatApp). Reconcile
                # by dropping it; fall through to the logged-out view.
                for k in ("uta_access_token", "uta_sub", "uta_id_token"):
                    request.session.pop(k, None)
        return HttpResponse('<a href="/login/">Log in with UseThatApp</a>')
    
    
    def logout(request):
        # Don't clear the session yet — the user may choose "Stay signed in" at
        # UseThatApp. Reconcile on return: a real logout revokes the token, so the
        # next get_entitlement (in home) returns 401 and we drop it then.
        id_token = request.session.get("uta_id_token")
        return redirect(logout_url(id_token=id_token, post_logout_redirect_uri="http://localhost:8000/"))

    Step 4 — URLs

    Wire the views into your URL conf. The callback path must match the path component of your registered UTA_REDIRECT_URI.

    urls.py
    from django.urls import path
    
    from . import views
    
    urlpatterns = [
        path("", views.home, name="home"),
        path("login/", views.login, name="login"),
        path("callback/", views.callback, name="callback"),
        path("logout/", views.logout, name="logout"),
    ]

    Gate on the live plan

    Gate features on the stable ent.product_public_id, not the mutable ent.version display name. The entitlement is always authoritative — a canceled license stops being entitled immediately.

    views.py
    def dashboard(request):
        token = request.session.get("uta_access_token")
        if not token:
            return redirect("login")
        ent = get_entitlement(token)
        if ent.entitled and ent.product_public_id == "prod_<your-pro-id>":
            return render(request, "dashboard_pro.html")
        return render(request, "dashboard_free.html")

    Offer the upgrade: purchase links

    When the entitlement check says the user is not entitled — or is on the free tier — send them to hosted checkout with purchase_url(). UseThatApp is the merchant of record (payment, tax, refunds), and the buyer comes back to your app signed in with the entitlement already live: no webhooks, no polling. Both purchase_url() and manage_url() are pure URL builders — no network call.

    views.py
    from usethatapp import get_entitlement, manage_url, purchase_url
    
    PRO_PRICE_ID = "<prc-price-id>"   # from get_prices() or your dashboard
    
    
    def account(request):
        token = request.session.get("uta_access_token")
        if not token:
            return redirect("login")
        ent = get_entitlement(token)
        if ent.entitled and ent.product_public_id == "prod_<your-pro-id>":
            # Already entitled — offer subscription management instead.
            manage = manage_url(next="http://localhost:8000/account/")
            return render(request, "account.html", {"manage": manage})
        # Not entitled — hosted checkout; the buyer returns signed in, entitled.
        buy = purchase_url(PRO_PRICE_ID, next="http://localhost:8000/welcome/")
        return render(request, "upgrade.html", {"buy": buy})

    For a live pricing page, render from get_prices() — never hardcode prices. Each Price carries a ready-made buy_url and the product_id that matches the entitlement’s product_public_id. See the Pricing API and Sell from your own website.

    Errors

    At the entitlement endpoint, 401 raises UtaTokenError (re-authenticate or refresh) and 403 raises UtaPermissionError (a valid token missing the entitlements scope). Every SDK error inherits from UtaError. See Error Handling and Gotchas.

    AI agents: this site is fully pre-rendered — fetch /llms.txt for an indexed summary of every page, or /llms-full.txt for the whole documentation as one Markdown file.