Built-in allauth providers are well-documented, but adding new ones can be tricky. I took some notes as a reminder to myself, and decided to share.
Making a Provider for Wix
Wix uses Oauth2 access and refresh tokens. Start by modifying another Oauth2 provider. In providers.py:
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider
class WixAccount(ProviderAccount):
pass
class WixProvider(OAuth2Provider):
id = 'wix'
name = 'Wix'
account_class = WixAccount
def extract_uid(self, data):
return str(data['id'])
provider_classes = [WixProvider]
urls.py:
from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import WixProvider
urlpatterns = default_urlpatterns(WixProvider)
Find the access URLs from the provider’s documentation. Like Wix, they may not be consistent. The authorize & access token URLs are unique, but you can pick any profile URL(s) with relevant details. Modify the header as needed – Wix uses the access token in the header as shown. In views.py:
from allauth.socialaccount.providers.oauth2.views import (
OAuth2Adapter,
OAuth2CallbackView,
OAuth2LoginView,
)
from .provider import WixProvider
class WixOAuth2Adapter(OAuth2Adapter):
provider_id = WixProvider.id
access_token_url = 'https://www.wixapis.com/oauth/access'
authorize_url = 'https://www.wix.com/installer/install'
profile_url = 'https://www.wixapis.com/apps/v1/instance'
supports_state = True
def complete_login(self, request, app, token, **kwargs):
resp = requests.get(
self.profile_url,
headers={'Authorization': token.token})
return self.get_provider().sociallogin_from_response(
request, extra_data
)
oauth2_login = OAuth2LoginView.adapter_view(WixOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(WixOAuth2Adapter)
Due to an odd redirect, Wix needs a custom client. You may be able to use the default and omit this file, client.py:
import requests
from django.utils.http import urlencode
from allauth.socialaccount.providers.oauth2.client import (
OAuth2Client,
OAuth2Error
)
class WixOAuth2Client(OAuth2Client):
def get_access_token(self, code):
resp = requests.post(
self.access_token_url,
headers={'Content-Type': 'application/json'},
json={
'grant_type': 'authorization_code', # omit redirect_uri
'client_id': self.consumer_key, # << add fields missing
'client_secret': self.consumer_secret, # << from allauth
'code': code
}
)
access_token = None
if resp.status_code in [200, 201]:
access_token = resp.json()
if not access_token or 'access_token' not in access_token:
raise OAuth2Error(f'Wix access error: {resp.content}')
return access_token
def get_redirect_url(self, authorization_url, extra_params):
params = {
'appId': self.consumer_key, # instead of client_id
'redirectUrl': self.callback_url # instead of redirect_uri
}
if self.state:
params['state'] = self.state
params.update(extra_params)
return f'{authorization_url}?{urlencode(params)}'
Pick a field returned by the provider to use as the user ID. I opted for the user’s site name. In provider.py:
class WixProvider(OAuth2Provider):
def extract_uid(self, data):
return str(data['site']['siteDisplayName'])
Extra data allows additional profile info. To get necessary Wix data, I make two calls at login. In views.py:
class WixOAuth2Adapter(OAuth2Adapter):
def complete_login(self, request, app, token, **kwargs):
resp = requests.get(
self.profile_url,
headers={'Authorization': token.token}
)
extra_data = resp.json()
resp = requests.get(
self.members_url,
headers={'Authorization': token.token}
)
extra_data.update(resp.json())
return self.get_provider().sociallogin_from_response(
request, extra_data
)
BONUS: Refresh token
Wix tokens persist for 5 minutes, but renew with a refresh token. While not part of the allauth provider, here’s a useful Wix token-refreshing decorator function. Use the decorator on any Wix API calls, and the token will refresh if needed.
from django.conf import settings
from functools import wraps
from rauth import OAuth2Service
def fresh_wix(func):
'refresh Wix token if necessary'
@wraps(func)
def fresh_func(self, *args):
out = func(self, *args)
if out.get('message') == 'internal error':
self.refresh()
return func(self, *args)
return out
return fresh_func
class WixAPI:
access_token_url='https://www.wixapis.com/oauth/access'
def __init__(self, access_token, refresh_token):
self.data = {
'client_id': settings.WIX_CLIENT_ID,
'client_secret': settings.WIX_CLIENT_SECRET,
'refresh_token': refresh_token,
'grant_type': 'refresh_token'
}
self.service = OAuth2Service(
client_id=settings.WIX_CLIENT_ID,
client_secret=settings.WIX_CLIENT_SECRET,
access_token_url=self.access_token_url,
)
self.session = self.service.get_session(access_token)
def refresh(self):
data = self.session.post(
self.access_token_url,
json=self.data,
headers={'Content-type': 'application/json'}
).json()
self.data['refresh_token'] = data['refresh_token']
self.session = self.service.get_session(data['access_token'])
@fresh_wix
def do_stuff(self, url):
return self.session.get(url) # Wix API call with fresh tokens
Find the code at github. Thanks for reading!