• Nov. 9, 2023, 1:12 p.m.

    I'm trying to set OAuth2 from Misago forum to my web service based on Django. I've installed Django Oauth Toolkit and configured it according to documentation. I've set authorization grant type to "authorization-code" of course configured Misago Oauth2 according instruction but I have communicat "Could not sign in with web service | The OAuth2 authorization code was not sent by the provider." In url I can see http://127.0.0.1:8000/oauth2/complete/?error=invalid_request&error_description=Code+challenge+required. When I looked into logs, there is no uri param code_challenge.

    Is Django Oauth Toolkit and authorization-code grant type good way to provide authorization for Misago?

  • Nov. 9, 2023, 1:26 p.m.
    check_box

    Marked as best answer by Nov. 9, 2023, 9:01 p.m..

    The problem here is that Misago expects authorization code grant type, and OAuth toolkit uses authorization code with PKCE extension grant, which is not supported.

    Ideally, there would be an option in Misago to enable PKCE, but until this happens you need to find a way to disable the PKCE in OAuth toolkit.

    Edit: You need to set PKCE_REQUIRED to False as per their docs.

    Roadmap item: github.com/rafalp/Misago/issues/1656

  • Nov. 9, 2023, 9:12 p.m.

    Thanks, this solves code_challenge problem but I have more errors:

    1. OAuth2StateNotSetError - web service returns state as GET param and the key is just state than oauth2_state.

    2. Connection refused - this is more complicated and I don't know solution yet. Bellow traceback:

    [09/Nov/2023 19:59:18] "GET /oauth2/login/ HTTP/1.1" 302 0
    OAuth2 Error
    Traceback (most recent call last):
      File "/usr/local/lib/python3.11/site-packages/urllib3/connection.py", line 174, in _new_conn
        conn = connection.create_connection(
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/urllib3/util/connection.py", line 95, in create_connection
        raise err
      File "/usr/local/lib/python3.11/site-packages/urllib3/util/connection.py", line 85, in create_connection
        sock.connect(sa)
    ConnectionRefusedError: [Errno 111] Connection refused
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 703, in urlopen
        httplib_response = self._make_request(
                           ^^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 398, in _make_request
        conn.request(method, url, **httplib_request_kw)
      File "/usr/local/lib/python3.11/site-packages/urllib3/connection.py", line 239, in request
        super(HTTPConnection, self).request(method, url, body=body, headers=headers)
      File "/usr/local/lib/python3.11/http/client.py", line 1286, in request
        self._send_request(method, url, body, headers, encode_chunked)
      File "/usr/local/lib/python3.11/http/client.py", line 1332, in _send_request
        self.endheaders(body, encode_chunked=encode_chunked)
      File "/usr/local/lib/python3.11/http/client.py", line 1281, in endheaders
        self._send_output(message_body, encode_chunked=encode_chunked)
      File "/usr/local/lib/python3.11/http/client.py", line 1041, in _send_output
        self.send(msg)
      File "/usr/local/lib/python3.11/http/client.py", line 979, in send
        self.connect()
      File "/usr/local/lib/python3.11/site-packages/urllib3/connection.py", line 205, in connect
        conn = self._new_conn()
               ^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/urllib3/connection.py", line 186, in _new_conn
        raise NewConnectionError(
    urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPConnection object at 0x7fac03f43010>: Failed to establish a new connection: [Errno 111] Connection refused
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/usr/local/lib/python3.11/site-packages/requests/adapters.py", line 489, in send
        resp = conn.urlopen(
               ^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/urllib3/connectionpool.py", line 787, in urlopen
        retries = retries.increment(
                  ^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/urllib3/util/retry.py", line 592, in increment
        raise MaxRetryError(_pool, url, error or ResponseError(cause))
    urllib3.exceptions.MaxRetryError: HTTPConnectionPool(host='127.0.0.1', port=8008): Max retries exceeded with url: /o/token/ (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fac03f43010>: Failed to establish a new connection: [Errno 111] Connection refused'))
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/srv/misago/misago/oauth2/client.py", line 64, in get_access_token
        r = requests.post(
            ^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/requests/api.py", line 115, in post
        return request("post", url, data=data, json=json, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/requests/api.py", line 59, in request
        return session.request(method=method, url=url, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/requests/sessions.py", line 587, in request
        resp = self.send(prep, **send_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/requests/sessions.py", line 701, in send
        r = adapter.send(request, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/local/lib/python3.11/site-packages/requests/adapters.py", line 565, in send
        raise ConnectionError(e, request=request)
    requests.exceptions.ConnectionError: HTTPConnectionPool(host='127.0.0.1', port=8008): Max retries exceeded with url: /o/token/ (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7fac03f43010>: Failed to establish a new connection: [Errno 111] Connection refused'))
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "/srv/misago/misago/oauth2/views.py", line 56, in oauth2_complete
        token = get_access_token(request, code_grant)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/srv/misago/misago/oauth2/client.py", line 71, in get_access_token
        raise exceptions.OAuth2AccessTokenRequestError()
    misago.oauth2.exceptions.OAuth2AccessTokenRequestError
    Bad Request: /oauth2/complete/
    Bad Request: /oauth2/complete/
    [09/Nov/2023 19:59:26] "GET /oauth2/complete/?code=4Ozxpfq0xMdtbdno9Ik2gSnaLEgYpi&state=aat7kyquGoZTbMcyoYov7uDS12AL7fmKGYwNrkGx HTTP/1.1" 400 18567
    
  • Nov. 9, 2023, 9:17 p.m.

    OAuth2StateNotSetError - web service returns state as GET param and the key is just state

    Misago expects state to be in state parameter of the URL so thats accurate.

    Also, states are single use. OAuth2StateNotSetError means that you've tried to F5 on Misago's side of things.

    Connection refused - this is more complicated and I don't know solution yet. Bellow traceback:

    You have a connection refused error. Is the url to your OAuth 2 server valid?

  • Nov. 10, 2023, 9:56 p.m.

    This is result of my changes in file github.com/rafalp/Misago/blob/main/misago/oauth2/client.py#L31C7-L31C7:

    session_state = request.session.pop(SESSION_STATE, None) or request.GET.get("state")
    

    Second issue was wrong address for docker container. This Misago in container tried to connect to 127.0.0.1:8008 but web service was on host. I set "Access token retrieval URL" to "host.docker.internal:8008/o/token/".

    As you mentioned to me in private chat I added to docker-compose.yaml:

    services:
      ...
      misago:
        ...
        extra_hosts:
        - "host.docker.internal:host-gateway"
    

    Now it almost works. I can login to Misago forum using web service but any reload web service page cause logout from Misago. Still working on it.

  • Nov. 10, 2023, 10:04 p.m.

    This change is invalid and makes Misago believe that all OAuth2 login flows were initialized by it. DON'T DO THIS

  • Nov. 10, 2023, 11:18 p.m.

    OK, @rafalp I agree with you. I removed this change, then I changed 127.0.0.1 to localhost in Misago OAuth2 configuration and it works.

    Configuration is based on Django Oauth Toolkit Documentation, Authorization grant type: "Authorization code".

    My configuration in Misago -> Settings -> OAuth2:

    Enable OAuth2 client: Yes
    Provider name: web service
    Client ID: (copied from web service)
    Client Secret: (copied form web service)
    Login from URL: http://localhost:8008/o/authorize/
    Scopes: email openid
    Access token retrieval URL: http://host.docker.internal:8008/o/token/
    JSON path to access token: access_token
    User data URL: http://host.docker.internal:8008/o/extuserinfo/
    Request method: GET
    Access token location: HTTP header (Bearer)
    Access token name: Authorization
    User ID path: id
    User name path: name
    User e-mail path: email
    

    On the other side web service based on Django and django-oauth-toolkit
    settings.py:

    ALLOWED_HOSTS = [
        'localhost',
        'host.docker.internal',
    ]
    
    INSTALLED_APPS = [
        'oauth.apps.OauthConfig',
        'oauth2_provider',
    ]
    
    OAUTH2_PROVIDER = {
        'SCOPES': {
            'openid': "OpenID Connect scope",
            'email': 'Address email',
        },
        'PKCE_REQUIRED': False,
        'OIDC_ENABLED': False,
    }
    
    MIDDLEWARE = [
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'oauth2_provider.middleware.OAuth2TokenMiddleware',
    ]
    
    AUTHENTICATION_BACKENDS = (
        'oauth2_provider.backends.OAuth2Backend',
    )
    

    web_service/urls.py:

    urlpatterns = [
        path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
        path('o/', include('oauth.urls', namespace='oauth')),
    ]
    

    oauth/urls.py:

    from django.urls import re_path
    
    from . import views
    
    app_name = "oauth"
    
    urlpatterns = [
        re_path(r"^extuserinfo/$", views.extuserinfo, name='extuserinfo'),
    ]
    

    oauth/views.py:

    from django.http import JsonResponse
    from django.contrib.auth.decorators import login_required
    
    
    @login_required()
    def extuserinfo(request, *args, **kwargs):
        user = request.user
        return JsonResponse({
            'email': user.email,
            'name': f"{user.first_name} {user.last_name}",
            'id': user.id,
        })
    

    oauth/apps.py:

    from django.apps import AppConfig
    
    
    class OauthConfig(AppConfig):
        name = 'oauth'
        verbose_name = 'Oauth2 extension'
    

    This above is main part of configuration responsible for OAuth2 provider. To connect to Misago I use url: http://127.0.0.1:8000/oauth2/login/ and to connect to my web service I use http://localhost:8008/. It is important not to use the same name because of cookies overwrite.

    Thanks @rafalp!