Coder Social home page Coder Social logo

aliev / aioauth Goto Github PK

View Code? Open in Web Editor NEW
210.0 13.0 16.0 3.33 MB

Asynchronous OAuth 2.0 provider for Python 3

Home Page: https://aliev.me/aioauth

License: MIT License

Python 98.47% Makefile 1.53%
oauth2 asyncio fastapi aiohttp python3 python oauth2-server asgi python-3

aioauth's Introduction

Asynchronous OAuth 2.0 framework for Python 3

Build Status Coverage License PyPi Python 3.7

aioauth implements OAuth 2.0 protocol and can be used in asynchronous frameworks like FastAPI / Starlette, aiohttp. It can work with any databases like MongoDB, PostgreSQL, MySQL and ORMs like gino, sqlalchemy or databases over simple BaseStorage interface.

Why this project exists?

There are few great OAuth frameworks for Python like oauthlib and authlib, but they do not support asyncio and rewriting these libraries to asyncio is a significant challenge (see issues here and here).

Supported RFCs

Installation

python -m pip install aioauth

FastAPI

FastAPI integration stored on separated aioauth-fastapi repository and can be installed via the command:

python -m pip install aioauth[fastapi]

aioauth-fastapi repository contains demo example which I recommend to look.

aioauth's People

Contributors

aliev avatar benreinhold-nm avatar danygielow avatar grmnz avatar gyusang avatar mgorven avatar synchronizing avatar woile avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aioauth's Issues

No client_secret passed to get_client for create_token_response in 1.5.2

In 1.5.2, if I call create_token_response with the client_secret in the POST parameters, then that value is not passed into the get_client function of the BaseStorage subclass. The same code works fine with 1.5.1.

Expected Result

The client_secret value is passed as the optional client_secret parameter in BaseStorage.get_client.

Actual Result

None is always passed

System Information

Python Version (3.x): 3.10
aioauth Version (0.x): 1.5.2
Framework (FastAPI / starlette, aiohttp, sanic, etc.): custom

Refactoring and multiple response_type support

As part of this task, I would like to carry out a small refactoring of the project to prepare for the openid integration (See: #26).

TODO List:

Passing extra query parameters to the request

What's the best way to pass custom query parameters to the Storage methods? For example, slack supports sending a team= query parameter for narrowing down oauth2 flows. I'd love to do something similar (with param 'channel'), but getting errors when trying to pass any extra query params:

...     query=Query(**query_params),
TypeError: __init__() got an unexpected keyword argument 'channel'

I understand the Query dataclass doesn't have support for it today, but is there any other good way to handle this? Or am I overcomplicating things?

p.s. – Thanks for the great work on this lib btw! πŸ™

`invalid_client` response is missing `application/json` content-type

The errors produced when passing invalid client credentials have a JSON body but not an application/json content-type (as prescribed by RFC 6749, Section 5.2). The cause for this is this line where the empty header directory "overwrites" the default_headers in the base class constructor.

Originally I wanted to make a quick PR to fix this, but there seems to be a test testing explictly for the content-type-less response header which is why I wanted to check here first that I'm not missing something.

Why a token is needed for `/authorize`?

Hi,

As far as I know (I'm not an OAuth2 expert, so please let me know if I'm wrong), the steps for AuthorizationCodeFlow should be as:

  1. User sends a request using the front channel to /authorize with data about the client like client_id, scope, redirect_uri, etc.
  2. The authorization server validates the client info and redirects the user to the login page, possibly showing a consent page afterward.
  3. If the user successfully logs in and accepts the consent info, he will be redirected back to the application with a code.
  4. The application makes a back channel request to the authorization server to exchange the code for a token at /token.

But it looks like you require a token even for the first step (request to /authorize):

In the validate_request method of ResponseTypeBase (inside the aioauth/response_type.py), there is this final check which ensures there is a user object in the request:

async def validate_request(self, request: TRequest) -> Client:
    # ...

    if not request.user:
        raise InvalidClientError[TRequest](
            request=request, description="User is not authorized"
        )

    return client

and also in the tests of the demo project you provided, you first generate a token and attach it to the request to the /authroize URL:

@pytest.mark.asyncio
async def test_authorization_code_flow(
    http_client: TestClient, user: "User", client: "Client"
):
    access_token, _ = get_jwt(user)

    response = await http_client.get(
        "/oauth2/authorize",
        query_string={
            "response_type": "code id_token",
            "client_id": client.client_id,
            "redirect_uri": client.redirect_uris[0],
            "scope": "openid email profile",
            "nonce": "73Ncd3",
        },
        headers={"Authorization": f"Bearer {access_token}"},
        allow_redirects=False,
    )
    
    # ...

How can I work around this issue?

Thank you in advance for your help. Please let me know if there's any additional information needed on my end.

Move FastAPI example from README.md to a separate directory

Description

The current project structure needs to be slightly tweaked.

This issue concerns only putting the FastAPI example into a separate mini-package. In this case users can run the example and try the library in action.

  • Move the FastAPI example into a separated directory from the README.md.
    • Think over what kind of temporary data storage can be used. The data storage can only store data in memory.
    • Create examples/fastapi directory.
    • Create examples/fastapi/README.md with description how run FastAPI project.
    • Create examples/fastapi/requirements.txt with all required requirements.

POST /authorize fails with `Method not allowed` status code

On the docstring of the create_authorization_response method, there's an example using a POST request:

aioauth/aioauth/server.py

Lines 311 to 316 in ab4f7ea

@app.post("/authorize")
async def authorize(request: fastapi.Request) -> fastapi.Response:
# Converts a fastapi.Request to an aioauth.Request.
oauth2_request: aioauth.Request = await to_oauth2_request(request)
# Creates the response via this function call.
oauth2_response: aioauth.Response = await server.create_authorization_response(oauth2_request)

However, the first thing that happens in that same method is the pre-validation that the request method is a GET:

self.validate_request(request, [RequestMethod.GET])

It's not that uncommon to split the GET /authorize and POST /authorize methods to handle authentication and consent in-between. For my specific use-case, the GET /authorize request will redirect the user to a client-side webpage to both handle authentication and app/client consent, and then the webpage would send the info back to the server as a POST /authorize request with additional headers proving the user is authenticated and can execute that oauth2 flow.

Let me know if this is outside the scope of the library at the moment.

Expected Result

Being able to send a POST request to the create_authorization_response method.

Actual Result

Getting a 405 status code with HTTP method is not allowed response

Reproduction Steps

import asyncio

from aioauth.collections import HTTPHeaderDict
from aioauth.config import Settings
from aioauth.requests import Post
from aioauth.requests import Query
from aioauth.requests import Request
from aioauth.server import AuthorizationServer
from aioauth.storage import BaseStorage
from aioauth.types import RequestMethod


class Storage(BaseStorage):
    pass


storage = Storage()
authorization_server = AuthorizationServer(storage)

oauth2_request = Request(
        settings=Settings(INSECURE_TRANSPORT=True),
        method=RequestMethod["POST"],
        headers=HTTPHeaderDict(),
        post=Post(),
        query=Query(),
        url="http://localhost:5001/oauth/authorize",
        user=None,
    )

loop = asyncio.get_event_loop()
result = loop.run_until_complete(authorization_server.create_authorization_response(oauth2_request))
print(result)
loop.close()

Result:

Response(content={'error': <ErrorType.METHOD_IS_NOT_ALLOWED: 'method_is_not_allowed'>, 'description': 'HTTP method is not allowed.', 'error_uri': ''}, status_code=<HTTPStatus.METHOD_NOT_ALLOWED: 405>, headers={'content-type': 'application/json', 'cache-control': 'no-store', 'pragma': 'no-cache', 'allow': 'GET'})

System Information

Python Version (3.9.13):
aioauth Version (1.4.0):
Framework (FastAPI)

Missing aiohttp example

the aiohttp example is missing also the Server & Database configuration, I am looking into aioauth and still don't know how to use it :(

About a Third-Party (external) authorization server interface

Dear @aliev and cons

Your library is amazing but, as you indeed mentioned is still missing some extra-docs for guidance on implementation.

I just would like to know If you have any example of an Third party implementation interface like typical Google, Microsoft auth ... and so on. The level of class abstractions make me go ahead so slow.

I suppose that the key is in creating an interface of Storage class and methods, to create the correct instance for each desired third party chosen.

Please any help would be amazing man.

Incompatible with Python >= 3.11

Hi @aliev, thanks for the package! It's just what I wanted for one of my personal projects.
Found an unfortunate bug due to using Python 3.11.

I believe this is due to python's dataclasses module changed the way of checking if the object is mutable.

In 3.11

    if f._field_type is _FIELD and f.default.__class__.__hash__ is None:
        raise ValueError(f'mutable default {type(f.default)} for field '
                         f'{f.name} is not allowed: use default_factory')

In 3.10

    if f._field_type is _FIELD and isinstance(f.default, (list, dict, set)):
        raise ValueError(f'mutable default {type(f.default)} for field '
                         f'{f.name} is not allowed: use default_factory')

It should be a pretty straightforward fix and I'll be more than happy to help

Expected Result

Be able to import the package.

Actual Result

Import failed with the exception (in the next paragraph).

Reproduction Steps

> make test

pytest tests
ImportError while loading conftest '/Users/daniel/opensource/aioauth/tests/conftest.py'.
tests/conftest.py:7: in <module>
    from aioauth.grant_type import (
aioauth/grant_type.py:11: in <module>
    from .errors import (
aioauth/errors.py:18: in <module>
    from .requests import TRequest
aioauth/requests.py:62: in <module>
    @dataclass
../../.pyenv/versions/3.11.1/lib/python3.11/dataclasses.py:1220: in dataclass
    return wrap(cls)
../../.pyenv/versions/3.11.1/lib/python3.11/dataclasses.py:1210: in wrap
    return _process_class(cls, init, repr, eq, order, unsafe_hash,
../../.pyenv/versions/3.11.1/lib/python3.11/dataclasses.py:958: in _process_class
    cls_fields.append(_get_field(cls, name, type, kw_only))
../../.pyenv/versions/3.11.1/lib/python3.11/dataclasses.py:815: in _get_field
    raise ValueError(f'mutable default {type(f.default)} for field '
E   ValueError: mutable default <class 'aioauth.collections.HTTPHeaderDict'> for field headers is not allowed: use default_factory
make: *** [test] Error 4

System Information

> python --version
Python 3.11.1

> pip show aioauth
Name: aioauth
Version: 1.5.0
Summary: Asynchronous OAuth 2.0 framework for Python 3.
Home-page: https://github.com/aliev/aioauth
Author: Ali Aliyev
Author-email: [email protected]
License: The MIT License (MIT)
Location: /Users/daniel/opensource/aioauth
Requires: typing_extensions
Required-by: 

OpenID Connect

Hello people, I like the project, but I was wondering if you have any plans to add OpenID Connect to it.
AFAIK OpenID Connect is built on top of Oauth2.0 to provide authentication, which Oauth2 doesn't provide (only authorization).

According to this talk is just a set of 4 extra steps. But I couldn't find any documentation.

Thanks!

Project Restructure

I have some ideas, but mostly thoughts, I'm still studying the library, so sorry if they don't make sense πŸ˜… haha

I was thinking maybe providing some store implementations + an in-memory implementation (to play with, at the moment the in-memory is part of the tests), kind of like fastapi-users does [1] [2]

Also I was wondering if this should be aio-auth or asgi-auth, being asgi means it should work with starlette, fastapi and any other asgi library (kind of like wsgi with flask, django, etc). But this is mostly a thought rather than an idea, not sure the impact, though asgi servers provide the request object [3].
But don't hold up to that thought, maybe is also better to provide interfaces for different frameworks, like it is documented now in the README. I still have to weight in the different approaches, and if an asgi approach is even possible (I think it would work for middlewares... but providing the endpoints may be super difficult, so maybe just to have each framework implementation isolated.

Maybe the user should do: pip install aioauth[sqlalchemy,starlette] ???

For the docs I'd use mkdocs.

I'd also recommend using the pyproject.toml format, or separating the requirements into normal + development requirements (wheel and twine are not "test", so semantically is a bit confusing).

I maintain commitizen, so if you were to use conventionalcommits + semver, you could get automatic releases + changelog [4] [5]

Thanks a lot for the package and I hope I can contribute more.
If I come up with any real useful idea I'll open a ticket and we can discuss there πŸ‘πŸ»

Originally posted by @woile in #27 (comment)

Pyright needs to be added to 'dev-install' make instruction.

Unsure how one would go about this, but the NPM package pyright needs to be added to the dev-install Makefile instruction. Simply doing npm install -g pyright will not suffice as Mac and Linux users require sudo, and it would be unknown whether or not the user has npm installed to begin with.

My best guess is documenting this is the best way around this. Opening issue as a form of discussion.

Authorization Code flow with PKCE loses cookies between redirects

  • aioauth version: ^1.6 (actual on pypi)
  • Python version: 3.9
  • Operating System: linux

Description

I'm building a opinionated all-in-one foundation for web app development, Red Warden.
I'm integrating aioauth in it, and I'm having issues on the AuthCode (PKCE) flow, as it loses the cookies between redirects.

What I Did

I'm using a cookie for session tracking. I assumed the flow involves a redirect if the user is not logged in. So, for example (I'm using Postman web app to execute the flow):

  • GET /oauth2/authorize?...
    the user is not logged in (no session cookie), redirects 302 to /login
  • GET /login
    I enter my credentials and submit
  • POST /login
    credentials are ok, sets a session cookie, redirects to /oauth2/authorize?...
  • GET /oauth2/authorize?...
    the user is logged in, processes correctly the request parameters, proceeds to /oauth2/token
  • POST /oauth2/token
    here the session cookie is lost, so it bounces back to /login

From what i can see the "to_oauth2_request" and "to_fastapi_response" do not care about cookies.
The first thoughts were:

  • subclass the OAuth2Request class and add a "cookies" property (my preferred choice);
  • remove the "logged in" requirement from the POST /token endpoint;

but I'm not sure it's the right way to go.

By the way, this library is awesome! Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.