Skip to content

Commit 84f1826

Browse files
committed
Fix: Provide a client which force basic_auth as the default
1 parent f2980a9 commit 84f1826

File tree

4 files changed

+78
-64
lines changed

4 files changed

+78
-64
lines changed

README.md

Lines changed: 22 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ This package provides a configurable, reusable Django app that allows users to s
1313
- Integrates seamlessly with `django-allauth`'s social account framework.
1414
- Supports API-first authentication flows via `dj-rest-auth`.
1515
- Configurable for both Vipps Test and Production environments via standard settings.
16-
- Allows customization of requested scopes.
16+
- Correctly handles Vipps' required `client_secret_basic` authentication method for REST APIs.
1717
- Fully tested and documented for a "drop-in" experience.
1818

1919
## 1. Installation & Setup
@@ -23,38 +23,25 @@ This package provides a configurable, reusable Django app that allows users to s
2323
```bash
2424
pip install django-allauth-vipps
2525
```
26+
2627
*(Or `poetry add django-allauth-vipps` if you use Poetry)*
2728

2829
### Step 2: Update `INSTALLED_APPS`
2930

30-
Add `vipps_auth` to your `INSTALLED_APPS` in your Django `settings.py`. It must be placed after the standard `allauth` apps.
31+
Add `vipps_auth` to your `INSTALLED_APPS` in your Django `settings.py`.
3132

3233
```python
3334
# settings.py
3435

3536
INSTALLED_APPS = [
36-
# ... other apps
37-
'django.contrib.admin',
38-
'django.contrib.auth',
39-
'django.contrib.contenttypes',
40-
'django.contrib.sessions',
41-
'django.contrib.messages',
42-
'django.contrib.staticfiles',
43-
'django.contrib.sites', # Required by allauth
44-
45-
# Allauth apps
37+
# ...
4638
'allauth',
4739
'allauth.account',
4840
'allauth.socialaccount',
49-
50-
# Add the Vipps provider app
5141
'vipps_auth',
5242
]
5343

54-
# Required by allauth
5544
SITE_ID = 1
56-
57-
# Ensure you have authentication backends configured
5845
AUTHENTICATION_BACKENDS = [
5946
'django.contrib.auth.backends.ModelBackend',
6047
'allauth.account.auth_backends.AuthenticationBackend',
@@ -63,40 +50,27 @@ AUTHENTICATION_BACKENDS = [
6350

6451
### Step 3: Configure the Provider
6552

66-
This package is configured using `django-allauth`'s standard `SOCIALACCOUNT_PROVIDERS` setting. This allows you to set your credentials, select the environment (test or production), and define what data you request from the user.
67-
68-
Add the following to your `settings.py`:
53+
Configure the provider using `django-allauth`'s standard `SOCIALACCOUNT_PROVIDERS` setting in your `settings.py`.
6954

7055
```python
7156
# settings.py
7257
import os
7358

7459
SOCIALACCOUNT_PROVIDERS = {
7560
'vipps': {
76-
# Method 1 (Recommended): Configure credentials directly in settings.
77-
# This is ideal for production and CI/CD environments.
61+
# Configure credentials using environment variables.
7862
'APPS': [
7963
{
8064
'client_id': os.getenv('VIPPS_CLIENT_ID'),
8165
'secret': os.getenv('VIPPS_CLIENT_SECRET'),
82-
'key': '' # Not used by Vipps
66+
'key': ''
8367
}
8468
],
8569

8670
# --- General Provider Settings ---
87-
88-
# For production, this must be False. For development, set to True
89-
# to use the Vipps test API ([https://apitest.vipps.no](https://apitest.vipps.no)).
90-
'TEST_MODE': False,
91-
92-
# This tells django-allauth to trust the email address received from Vipps.
71+
'TEST_MODE': False, # Set to True for development/testing
9372
'VERIFIED_EMAIL': True,
94-
95-
# (Recommended) Enforce that the login fails if Vipps has not
96-
# verified the user's email address on their end.
9773
'EMAIL_VERIFIED_REQUIRED': True,
98-
99-
# Define the specific user data (scopes) you want to request.
10074
'SCOPE': [
10175
'openid',
10276
'name',
@@ -106,19 +80,17 @@ SOCIALACCOUNT_PROVIDERS = {
10680
}
10781
}
10882
```
109-
> **Important:** For credentials, choose **one** method. Either use the `APPS` key in `settings.py` (recommended) or create a `SocialApp` in the Django Admin as described in the `django-allauth` documentation. Using both for the same provider will cause an error.
83+
> **Important:** For credentials, either use the `APPS` key in `settings.py` (recommended) or create a `SocialApp` in the Django Admin. Using both for the same provider will cause an error.
11084
11185
### Step 4: Configure on Vipps Developer Portal
11286

113-
1. Log in to the [Vipps MobilePay Developer Portal](https://portal.vippsmobilepay.com/).
114-
2. Navigate to the "Developer" section and get your credentials for a sales unit.
115-
* **Client ID** (goes into `VIPPS_CLIENT_ID` environment variable)
116-
* **Client Secret** (goes into `VIPPS_CLIENT_SECRET` environment variable)
117-
3. In the **"Redirect URIs"** section, add the URL that Vipps will redirect users back to.
118-
* **Standard Web Flow:** `https://yourdomain.com/accounts/vipps/login/callback/`
119-
* **API/SPA Flow:** This should be the URL of your *frontend* application that handles the final redirect, e.g., `https://my-react-app.com/auth/callback/vipps`
87+
1. Log in to the [Vipps MobilePay Developer Portal](https://portal.vippsmobilepay.com/).
88+
2. Get your **Client ID** and **Client Secret**.
89+
3. Set the **Token endpoint authentication method** to **`client_secret_basic`**.
90+
4. Add your **Redirect URI** (`https://yourdomain.com/accounts/vipps/login/callback/` for web flows, or your frontend URL for API flows).
91+
92+
### Step 5: Run Migrations
12093

121-
### Step 5: Run Database Migrations
12294
```bash
12395
python manage.py migrate
12496
```
@@ -127,45 +99,32 @@ python manage.py migrate
12799

128100
### For Traditional Django Websites
129101

130-
If you are using server-rendered templates, add a Vipps login button with the `provider_login_url` template tag.
131-
132-
**In your template (`login.html`):**
102+
Use the `provider_login_url` template tag.
133103
```html
134104
{% load socialaccount %}
135-
136-
<h2>Login</h2>
137105
<a href="{% provider_login_url 'vipps' %}">Log In with Vipps</a>
138106
```
139107

140108
### For REST APIs (with `dj-rest-auth`)
141109

142-
This is the standard flow for Single-Page Applications (React, Vue, etc.).
110+
When using `dj-rest-auth`, you must use the custom `VippsOAuth2Client` provided by this package to ensure the correct authentication method (`client_secret_basic`) is used.
143111

144-
In your project's `urls.py`, create a login view that uses the `VippsOAuth2Adapter`.
112+
In your project's `urls.py`, create your login view like this:
145113

146114
```python
147115
# your_project/urls.py
148116
from django.urls import path
149117
from dj_rest_auth.registration.views import SocialLoginView
150118
from vipps_auth.views import VippsOAuth2Adapter
119+
from vipps_auth.client import VippsOAuth2Client # <-- Import the custom client
151120

152-
# This view connects dj-rest-auth to our Vipps adapter.
153-
# No client_class is needed unless you have advanced requirements.
121+
# This view connects dj-rest-auth to our Vipps adapter
154122
class VippsLoginAPI(SocialLoginView):
155123
adapter_class = VippsOAuth2Adapter
156-
# This MUST match the redirect URI you set in the Vipps Portal for your frontend
124+
client_class = VippsOAuth2Client # <-- Use the custom client here
157125
callback_url = "YOUR_FRONTEND_CALLBACK_URL"
158126

159127
urlpatterns = [
160128
# ... your other urls
161129
path("api/v1/auth/vipps/", VippsLoginAPI.as_view(), name="vipps_login_api"),
162-
]
163-
```
164-
165-
## 3. Development & Testing
166-
167-
To work on this package locally:
168-
1. Clone the repository: `git clone https://github.com/danpejobo/django-allauth-vipps.git`
169-
2. Install dependencies: `poetry install`
170-
3. Activate the virtual environment: `poetry shell`
171-
4. Run the test suite: `poetry run pytest`
130+
]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "django-allauth-vipps"
3-
version = "0.2.0"
3+
version = "0.2.1"
44
description = "A configurable Django Allauth provider for Vipps Login."
55
authors = ["Daniel Persen <[email protected]>"]
66
license = "MIT"

tests/test_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# tests/test_client.py
2+
import pytest
3+
from django.test import RequestFactory
4+
from allauth.socialaccount.models import SocialApp
5+
from django.contrib.sites.models import Site
6+
from vipps_auth.client import VippsOAuth2Client
7+
8+
@pytest.fixture
9+
def social_app(db):
10+
"""A fixture that creates a Vipps SocialApp instance for tests."""
11+
app = SocialApp.objects.create(
12+
provider="vipps",
13+
name="Vipps Test",
14+
client_id="test_client_id",
15+
secret="test_secret",
16+
)
17+
app.sites.add(Site.objects.get_current())
18+
return app
19+
20+
@pytest.mark.django_db
21+
def test_vipps_oauth2_client_sets_basic_auth(social_app):
22+
"""
23+
Verify that the custom VippsOAuth2Client correctly sets
24+
the `basic_auth` flag to True.
25+
"""
26+
request = RequestFactory().get('/')
27+
28+
# Instantiate the client, which is the core of what we're testing.
29+
# The arguments are required by the parent OAuth2Client's __init__ method.
30+
client = VippsOAuth2Client(
31+
request=request,
32+
consumer_key=social_app.client_id,
33+
consumer_secret=social_app.secret,
34+
access_token_method='POST', # This can be any valid method for the test
35+
access_token_url='https://example.com/token',
36+
callback_url='https://example.com/callback',
37+
)
38+
39+
# The main assertion: ensure basic_auth is forced to True.
40+
assert client.basic_auth is True

vipps_auth/client.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# vipps_auth/client.py
2+
3+
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
4+
5+
class VippsOAuth2Client(OAuth2Client):
6+
"""
7+
Custom client to force the 'client_secret_basic' authentication method,
8+
which sends credentials in the Authorization header. This is required
9+
by Vipps when the client is configured for this method, which is the
10+
default setting in vipps.
11+
"""
12+
def __init__(self, *args, **kwargs):
13+
# This flag tells allauth to use HTTP Basic Auth.
14+
kwargs['basic_auth'] = True
15+
super().__init__(*args, **kwargs)

0 commit comments

Comments
 (0)