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, orapi). - 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-gitlablibrary 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 ahttp://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
Confidentialon, 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 forapiwhen you actually write to GitLab. - Pin redirect URIs to exact HTTPS URLs, and rotate the secret immediately if a leak is suspected.
- Validate
stateon 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.