GitLab OAuth and API Integration in a Django Project

Blog / Django · July 18, 2014 · Updated June 10, 2026 · 9 min read
GitLab OAuth and API Integration in a Django Project

To add GitLab login and GitLab API access to a Django project in 2026, don't hand-roll the OAuth dance. Install django-allauth, enable its built-in GitLab provider, register an OAuth application in GitLab (Settings → Applications), and let allauth run the OAuth 2.0 Authorization Code flow with PKCE for you. After a user signs in, allauth stores the access token, and you call the GitLab REST API v4 with an Authorization: Bearer <token> header — ideally through the python-gitlab client. The same pattern works for gitlab.com and self-managed GitLab once you point the provider at the right base URL.

The original version of this article used GitLab API v3 and a manual requests flow. API v3 was removed years ago — everything below targets API v4 and the OAuth patterns GitLab supports today.

Key takeaways

  • Use a library, not raw HTTP. django-allauth (or python-social-auth) handles GitLab OAuth 2.0, PKCE, state, and token storage so you don't reinvent auth.
  • Authorization Code + PKCE is the correct flow for a server-side Django app; PKCE is now expected even for confidential clients.
  • Register an OAuth application in GitLab, set exact redirect URIs, and request only the scopes you need (read_user, openid, read_api, or api).
  • One pattern, both deployments: the same code targets gitlab.com or any self-managed GitLab by changing the base URL.
  • Call API v4 with a Bearer token; the python-gitlab library wraps it cleanly and handles pagination.
  • Guard secrets: never ship the client secret to the browser, store tokens encrypted, and refresh expired tokens.

How do you add GitLab login to a Django app?

django-allauth ships a first-class GitLab provider, so the integration is mostly configuration. Install the package, add the apps, register the provider, and point it at your GitLab instance.

# pip install django-allauth python-gitlab

# settings.py
INSTALLED_APPS = [
    # ...
    "django.contrib.sites",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.gitlab",
]

SITE_ID = 1

AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
]

SOCIALACCOUNT_PROVIDERS = {
    "gitlab": {
        # self-managed: "https://gitlab.example.com"
        "GITLAB_URL": "https://gitlab.com",
        "SCOPE": ["openid", "read_user", "read_api"],
        "OAUTH_PKCE_ENABLED": True,  # Authorization Code + PKCE
    }
}

How do you register a GitLab application?

In GitLab, open your avatar → Settings → Applications for a personal app, or use Group → Settings → Applications (or the admin area) for org-wide use. Create the application with:

  • Name — anything recognizable, e.g. Acme Django SSO.
  • Redirect URI — must match exactly, e.g. https://app.example.com/accounts/gitlab/login/callback/ (allauth's default callback). Add a http://localhost:8000/... entry for local development.
  • Confidential — keep this on for a server-side Django app so the client secret never leaves the server.
  • Scopes — pick the minimum you need (see the table below).

GitLab then gives you an Application ID (client id) and Secret. Store both in environment variables, then register them with allauth — either through the Django admin (Social applications) or directly in settings.

import os

# settings.py — credentials from the environment
SOCIALACCOUNT_PROVIDERS["gitlab"]["APPS"] = [
    {
        "client_id": os.environ["GITLAB_CLIENT_ID"],
        "secret": os.environ["GITLAB_CLIENT_SECRET"],
    }
]

# Recent allauth versions require this middleware
MIDDLEWARE += ["allauth.account.middleware.AccountMiddleware"]

# urls.py
from django.urls import include, path

urlpatterns = [
    path("accounts/", include("allauth.urls")),
    # ... your routes
]

Which GitLab OAuth scopes should you request?

Request the narrowest scope that covers your use case. Read scopes are safer than write scopes, and api is broad — avoid it unless you genuinely write to GitLab.

Scope Grants Use it when
openid OpenID Connect sign-in + stable sub identifier You only need authentication (SSO)
read_user Read the signed-in user's profile and email You show "Sign in with GitLab" and store basic profile
read_api Read-only access to most API v4 resources You list a user's projects, issues, merge requests
api Full read/write API v4 access You create issues, push, or edit MRs on the user's behalf
read_repository Read repository files over HTTPS/API You read file contents or trees

For a typical "log in with GitLab and show my projects" feature, ["openid", "read_user", "read_api"] is enough.

django-allauth vs python-social-auth vs manual OAuth

All three can authenticate against GitLab. The difference is how much of OAuth 2.0, PKCE, token storage, and edge cases you maintain yourself.

Concern django-allauth python-social-auth Manual requests-oauthlib
GitLab provider Built-in Built-in (gitlab backend) You write it
PKCE Supported via setting Supported You implement it
Token storage SocialToken model UserSocialAuth model You design it
Self-managed GitLab GITLAB_URL setting GITLAB_API_URL setting You set every URL
Account linking / signup Built-in flows + templates Built-in pipeline You build it
Best for Most Django apps Pipeline-style customization Edge cases / learning

For most teams, django-allauth is the default choice in 2026: it is actively maintained, has the largest provider catalogue, and ships the login, signup, and connection templates. Reach for python-social-auth when you want its customizable authentication pipeline. Only drop down to requests-oauthlib by hand when you have an unusual requirement the libraries can't express — and accept that you then own every security edge case.

What OAuth flow and PKCE settings should you use?

Use the OAuth 2.0 Authorization Code flow with PKCE. The browser is redirected to GitLab's /oauth/authorize, the user approves, GitLab redirects back to your callback with a short-lived code, and your server exchanges that code (plus the PKCE code_verifier and client secret) at /oauth/token for an access token and refresh token. With allauth, OAUTH_PKCE_ENABLED = True turns this on — you never touch the raw endpoints.

If you ever do it by hand, the authorize and token calls look like the snippet below.

# Reference only — django-allauth performs these calls for you.

# 1. Redirect the user to GitLab (Authorization Code + PKCE)
#    GET https://gitlab.com/oauth/authorize
#        ?client_id=APP_ID
#        &redirect_uri=https://app.example.com/accounts/gitlab/login/callback/
#        &response_type=code
#        &scope=openid+read_user+read_api
#        &state=RANDOM
#        &code_challenge=BASE64URL_SHA256(verifier)
#        &code_challenge_method=S256

# 2. Exchange the returned code for tokens
import os
import requests

def exchange_code(code, verifier):
    resp = requests.post(
        "https://gitlab.com/oauth/token",
        data={
            "client_id": os.environ["GITLAB_CLIENT_ID"],
            "client_secret": os.environ["GITLAB_CLIENT_SECRET"],
            "code": code,
            "grant_type": "authorization_code",
            "redirect_uri": "https://app.example.com/accounts/gitlab/login/callback/",
            "code_verifier": verifier,
        },
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()  # access_token, refresh_token, expires_in, ...

How do you call the GitLab REST API v4 with the token?

After login, allauth stores the OAuth token in its SocialToken table. Pull that token and send it as a Bearer credential against the API v4 base path (/api/v4). The python-gitlab library is the cleanest way to do this — it wraps authentication, pagination, and resource objects so you work with Python objects instead of raw JSON.

# Raw-HTTP equivalent of the calls below:
#   requests.get("https://gitlab.com/api/v4/user",
#                headers={"Authorization": f"Bearer {access_token}"})

import gitlab
from allauth.socialaccount.models import SocialToken

def gitlab_client_for(user):
    """Build an OAuth-authenticated python-gitlab client for a logged-in user."""
    token = (
        SocialToken.objects
        .filter(account__user=user, account__provider="gitlab")
        .latest("id")
    )
    return gitlab.Gitlab(
        url="https://gitlab.com",   # self-managed: your instance URL
        oauth_token=token.token,
        api_version="4",            # python-gitlab default
    )

def list_my_projects(user):
    gl = gitlab_client_for(user)
    gl.auth()                       # populates gl.user
    me = gl.user
    # membership=True -> only projects the user belongs to
    projects = gl.projects.list(membership=True, get_all=True)
    return me.username, [(p.id, p.path_with_namespace) for p in projects]

How do you handle token refresh and self-managed GitLab?

Token refresh. GitLab OAuth access tokens expire (commonly after two hours), and the token response includes a refresh_token. When an API call returns 401, POST to /oauth/token with grant_type=refresh_token to get a fresh pair, then update the stored SocialToken. Treat refresh tokens as one-time-use where rotation is enabled — always persist the new refresh token you get back.

Self-managed GitLab. Nothing structural changes — point every base URL at your instance. Set GITLAB_URL in SOCIALACCOUNT_PROVIDERS, register the OAuth application on that instance, and pass the same host to gitlab.Gitlab(url=...). The OAuth flow, scopes, and API v4 paths are identical across gitlab.com Free, Premium, and Ultimate and self-managed installs.

def refresh_gitlab_token(social_token):
    """Exchange a refresh token for a new access/refresh pair and persist it."""
    resp = requests.post(
        "https://gitlab.com/oauth/token",
        data={
            "client_id": os.environ["GITLAB_CLIENT_ID"],
            "client_secret": os.environ["GITLAB_CLIENT_SECRET"],
            "grant_type": "refresh_token",
            "refresh_token": social_token.token_secret,  # allauth stores it here
        },
        timeout=10,
    )
    resp.raise_for_status()
    data = resp.json()
    social_token.token = data["access_token"]
    social_token.token_secret = data["refresh_token"]  # persist the rotated token
    social_token.save(update_fields=["token", "token_secret"])
    return social_token

OAuth token vs personal vs project/group access token

OAuth user tokens are right for "act as the logged-in user." For server-to-server automation that isn't tied to a person, a different token type is safer.

Token type Acts as Lifetime Best for
OAuth user token The signed-in user Short-lived + refresh User-facing SSO and "my projects" features
Personal access token (PAT) A specific user account You set an expiry Quick scripts, one-off automation
Project access token A single project (bot user) You set an expiry CI/CD or an app scoped to one repo
Group access token A whole group (bot user) You set an expiry Automation across many projects

How do you keep the integration secure?

  • Never expose the client secret. Keep Confidential on, hold the secret in environment variables or a secrets manager, and keep it server-side — it must never reach browser code.
  • Encrypt tokens at rest. Access and refresh tokens are credentials; encrypt the columns (for example with Fernet-based field encryption) or store them in a vault.
  • Request least privilege. Prefer read_* scopes; only ask for api when you actually write to GitLab.
  • Pin redirect URIs to exact HTTPS URLs, and rotate the secret immediately if a leak is suspected.
  • Validate state on the callback (allauth does this for you) to block CSRF.

Where this fits in a real Django app

GitLab SSO is usually one piece of a broader authentication and developer-tooling story. The same allauth foundation supports GitHub, Google, and SAML providers, so you can offer several login options from one configuration — see our walkthrough on integrating the GitHub API in Python Django and on single sign-on with Auth0 in Django. If your token sync or API calls run on a schedule, the patterns in using Django Celery with social auth keep that work off the request cycle.

At MicroPyramid we have shipped OAuth, SSO, and API integrations like this across many Django development and Python development projects — for both gitlab.com and self-managed GitLab behind a firewall.

Frequently Asked Questions

Should I use django-allauth or write the OAuth flow myself?

Use django-allauth. It ships a maintained GitLab provider, supports PKCE, stores tokens in its SocialToken model, and includes login, signup, and connection templates. Hand-rolling OAuth with requests-oauthlib only makes sense for unusual requirements the library can't express, and it means you own every security edge case yourself.

Does this work with self-managed GitLab, not just gitlab.com?

Yes. Set GITLAB_URL in SOCIALACCOUNT_PROVIDERS to your instance (for example https://gitlab.example.com), register the OAuth application on that instance, and pass the same URL to gitlab.Gitlab(url=...). The flow, scopes, and API v4 paths are identical across gitlab.com and self-managed Free, Premium, and Ultimate.

What scopes do I need to log a user in and list their projects?

openid and read_user cover authentication and basic profile, and read_api adds read-only access to list projects, issues, and merge requests. Only request api if you write back to GitLab. Always pick the narrowest scope that covers the feature.

Why is GitLab API v3 gone, and what replaced it?

API v3 was removed years ago; API v4 is the current REST API and lives under /api/v4. Any old tutorial calling /api/v3/user will fail. Point requests at /api/v4, or use python-gitlab with api_version="4", which is its default.

How do I refresh an expired GitLab access token?

When an API call returns 401, POST to /oauth/token with grant_type=refresh_token and the stored refresh token, then save the new access and refresh tokens back to the SocialToken record. GitLab access tokens are short-lived, so build refresh handling in from the start rather than as an afterthought.

Should I use an OAuth token or a personal access token?

Use an OAuth user token when actions are performed on behalf of the logged-in user (SSO, "show my projects"). For server-side automation that isn't tied to a person — CI jobs or scheduled syncs — prefer a project or group access token scoped to exactly what the bot needs, which limits the blast radius if it leaks.

Share this article