Code Monkey home page Code Monkey logo

loginpass's Introduction

Authlib Loginpass

Social connections powered by Authlib. This library is a part of Authlib project. It works well with Authlib v0.14.3+.

Build Status PyPI Version Follow Twitter

from flask import Flask
from authlib.integrations.flask_client import OAuth
from loginpass import create_flask_blueprint
from loginpass import Twitter, GitHub, Google

app = Flask(__name__)
oauth = OAuth(app)

def handle_authorize(remote, token, user_info):
    if token:
        save_token(remote.name, token)
    if user_info:
        save_user(user_info)
        return user_page
    raise some_error

backends = [Twitter, GitHub, Google]
bp = create_flask_blueprint(backends, oauth, handle_authorize)
app.register_blueprint(bp, url_prefix='')

Useful Links

Features

Authlib Loginpass contains lots of connections (see below), every connection has a profile() method which returns the same format of user info. It supports OAuth 1, OAuth 2 and OpenID Connect for now.

The user info that profile() returns is standardized with OpenID Connect UserInfo claims, not something made by me.

Connections

Connections that Authlib Loginpass contains:

  • Battle.net
  • Google
  • GitHub
  • Gitlab
  • Twitter
  • Facebook
  • Dropbox
  • Reddit
  • Linkedin
  • Azure
  • Discord
  • Slack
  • Jira
  • StackOverflow
  • Bitbucket
  • Auth0
  • Strava
  • Spotify
  • Yandex
  • Twitch
  • VK
  • Ory Hydra

Usage

Loginpass is just a simple wrapper around Authlib, it is configured apps ready to use with Flask and Django. Checkout the examples for details.

Alternatives

Most of the time, you don't have to use loginpass, you can just register a remote app with Authlib's framework integrations. Checkout our demo on OAuth clients for Flask, Django, Starlette and FastAPI:

https://github.com/authlib/demo-oauth-client

License

Loginpass is a group member of Authlib, it is licensed under BSD. Authlib commercial license applies to this project too, you can get a commercial license at Authlib Commercial Plans.

loginpass's People

Contributors

andriyor avatar andvikt avatar bnewbold avatar cburmeister avatar d0ugal avatar davidism avatar euri10 avatar jamielennox avatar kairichard avatar kujio avatar lepture avatar matthdsm avatar pleshevskiy avatar rdbisme avatar salty-horse avatar telepenin avatar watdev2018 avatar xofbd avatar zaghaghi 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

loginpass's Issues

FastAPI demo doesn't work

FastAPI demo doesn't work. I get error:

[...]
/lib/python3.7/site-packages/loginpass-0.5-py3.7.egg/loginpass/_fastapi.py", line 72, in auth
    return await handle_authorize(remote, token, user_info, request)
TypeError: object UserInfo can't be used in 'await' expression

FastAPI version:

$ pip freeze | grep fastapi
fastapi==0.67.0
$ pip freeze | grep loginpass
loginpass==0.5

[Feature Request] Add Patreon OAuth

I find it odd that you have a Patreon link on the repo but no Patreon OAuth client.

Yes, I know that I could add it myself… but thought perhaps it’s a good idea for you to add it also!

linkedin issues

trying to connect to linkedin I got first the following error:

authlib.common.errors.AuthlibBaseError: invalid_request: A required parameter "client_id" is missing
I solved it adding to my client_id and client_secret the below client_kwargs
LINKEDIN_CLIENT_ID = ''
LINKEDIN_CLIENT_SECRET = ''
LINKEDIN_CLIENT_KWARGS= {'token_endpoint_auth_method':'client_secret_post'}

so now I can get code and state but on the profile retrieval I got this error I dont understand:

INFO:werkzeug:127.0.0.1 - - [21/Aug/2018 09:58:44] "GET /linkedin/auth?code=AQRkLA5YlLDs23IwvTiEdupoQ-n-5PzgHBhQv3H7WlYfI1GR0HlmlfO8zPl5cyqksGzkgOZ1Jcqub2YIEEycJzfPmvKMDfTzO8qNFeH08I5C6G4_LDNE8beB6NJS1tBorxQJ2jLuZKBwdkNhTXn4pO4iBz0ZrtNdCx6sNDJj&state=NBEbA7D8TwXqD7t2XtcrMzwRxnCwED HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 2309, in __call__
    return self.wsgi_app(environ, start_response)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 2295, in wsgi_app
    response = self.handle_exception(e)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 1741, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
    raise value
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/loginpass/_core.py", line 121, in auth
    user_info = remote.profile()
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/loginpass/linkedin.py", line 35, in profile
    resp = self.get(url, **kwargs)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/authlib/client/client.py", line 212, in get
    return self.request('GET', url, **kwargs)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/authlib/flask/client/oauth.py", line 209, in request
    method, url, token=token, **kwargs)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/authlib/client/client.py", line 203, in request
    return session.request(method, url, **kwargs)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/authlib/client/oauth2_session.py", line 300, in request
    method, url, auth=auth, **kwargs)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/requests/sessions.py", line 498, in request
    prep = self.prepare_request(req)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/requests/sessions.py", line 441, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/requests/models.py", line 313, in prepare
    self.prepare_auth(auth, url)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/requests/models.py", line 544, in prepare_auth
    r = auth(self)
  File "/home/lotso/.local/share/virtualenvs/walkabout/lib/python3.6/site-packages/authlib/client/oauth2_auth.py", line 39, in __call__
    token_type = self.token['token_type']
KeyError: 'token_type'

Services loginpass should support

  • Google
  • GitHub
  • Gitlab
  • Twitter
  • Facebook
  • Dropbox
  • Reddit
  • Linkedin
  • Azure
  • Discord
  • Slack
  • Jira
  • StackOverflow
  • Bitbucket
  • Auth0
  • Trello
  • Twitch

Typo loginpass/_flask.py

The import on line 83 (

from authlib.integrations.flask_client import FlaskRemoteApp
) should read
from authlib.integrations.flask_client import RemoteApp as FlaskRemoteApp
instead of from authlib.integrations.flask_client import FlaskRemoteApp with authlib 0.14.

GitLab OAuth Always defaults to 'read_user' permission

in gitlab.py

line 35: 'client_kwargs': {'scope': 'read_user'},

this causes a lot of confusion when you set your scope in the GitLab Application Register to 'api' for example, which is a higher scope. Then every request made using this wrapper defaults to 'read_user' so you get 'access denied'

I would suggest the parent method
def create_gitlab_backend(name, hostname):
takes in scope as a variable to prevent future confusion

Lacking way to add OAuth parameter

Currently, there's no way to add OAuth parameter like hd in Google 1.

I have to do things like below to add the parameter :(

 # Creates OAuth from blueprint:
+Google.OAUTH_CONFIG["authorize_url"] += "&hd=example.com"
+
 oauth = OAuth(app)
 bp = create_flask_blueprint(Google, oauth, handle_authorize)
 app.register_blueprint(bp, url_prefix="/google")

Support using custom configuration options

Case in point, I want to do this:

AUTHLIB_OAUTH_CLIENTS = {
    'google': {
        'client_id': '...',
        'client_secret': '...',
        'client_kwargs': {
            'scope': 'https://www.googleapis.com/auth/gmail.readonly',
            'include_granted_scopes': 'true',
        },
    },
}

scope is used, as expected, but include_granted_scopes is discarded, which is unexpected.

deprecated LinkedIn v1 API

We use loginpass in our organization and noticed last week that people were unable to login using LinkedIn. Logs showed a 410 which lead us to realize that LinkedIn has a v2 for their web API and more than likely had deprecated v1, which loginpass uses.

We actually forked loginpass and made changes to get it to work. It does appear to be working now so the plan is to soon open up PR with the changes. Here's the repo of our fix: https://github.com/thedataincubator/loginpass

Improve readme

Would it be possible to give some details about how to use this "loginpass"?
I am using Flask and in the end, I created a new file (client.py) with the code example in your readme, add a secret key, call this client.py with Flask, then go to url github/login, but I am getting an error from github.
I tried again with gitlab (and modified my file client.py) but here also I am getting an error from gitlab. So I am not sure I am doing things correctly.

And by the way, what would be interesting for me is rather a client example which works with the oauth server defined in https://github.com/authlib/example-oauth2-server.

Loginpass fails with import error

I rebuilt my viritualenv with pipenv which worked fine before, and installed 0.5 version. However, when I try to run my app I get the following error:

Traceback (most recent call last):
  File "main.py", line 6, in <module>
    app = create_app(config)
  File "/Users/blackdwarf/Projects/x/x/__init__.py", line 34, in create_app
    backends=backends, oauth=oauth, handle_authorize=handle_authorization)
  File "/Users/blackdwarf/.virtualenvs/x-XUoYXvFU/lib/python3.7/site-packages/loginpass/_flask.py", line 38, in create_flask_blueprint
    register_to(oauth, b)
  File "/Users/blackdwarf/.virtualenvs/x-XUoYXvFU/lib/python3.7/site-packages/loginpass/_flask.py", line 83, in register_to
    from authlib.integrations.flask_client import FlaskRemoteApp
ImportError: cannot import name 'FlaskRemoteApp' from 'authlib.integrations.flask_client' (/Users/blackdwarf/.virtualenvs/scholarly-XUoYXvFU/lib/python3.7/site-packages/authlib/integrations/flask_client/__init__.py)

Does anyone know what is happening? Thanks!

Invalid claim when signing in with Google

from flask import Flask, render_template, jsonify
from authlib.flask.client import OAuth
from loginpass import create_flask_blueprint
from loginpass import Google

app = Flask(__name__)
app.config.from_pyfile('config.py')
app.secret_key = 'something secret'

def handle_authorize(remote, token, user_info):
    return jsonify(user_info)

@app.route('/')
def index():
    return render_template('login.html')

oauth = OAuth(app)

bp = create_flask_blueprint(Google, oauth, handle_authorize)
app.register_blueprint(bp, url_prefix='/')

Going to /login, signing in with Google, returns the error:
authlib.jose.errors.InvalidClaimError: invalid_claim: Invalid claim "azp"

Am I missing something?

Async version?

Is it possible to have async version of the various integrations? Thanks

Twitter example is failing

In the file loginpass/_flask.py and in the code:

id_token = request.args.get('id_token')
import pdb; pdb.set_trace()
if request.args.get('code'):
    token = remote.authorize_access_token()
    if id_token:
        token['id_token'] = id_token
elif id_token:
    token = {'id_token': id_token}
else:
    # handle failed

at the breakpoint, request.args returns an immutable dict with keys: oauth_token and oauth_verifier which are not handled. Is this expected?

LinkedIn does not return email.

Using the default LinkedIn implementation, no email address is returned.

For example:

oauth = OAuth()


def login_via_linkedin(request, user_info):
    user = None
    print(user_info) // returns all the user_profile fields, but no email


def handle_authorize(request, remote, token, user_info=None):
    print(remote.name)
    if user_info is None:
        return redirect("http://localhost:8000/login-failed")

    if remote.name == LinkedIn.OAUTH_NAME:
        login_via_linkedin(request, user_info)
    else:
        raise ValueError(
            f"Tried to log in with an unsupported remote server {remote.name}."
        )

    return redirect("http://localhost:8000/")


urlpatterns = [
    path(
        "linkedin/",
        include(create_django_urlpatterns(LinkedIn, oauth, handle_authorize)),
    ),
]

My LinkedIn application also includes the email address permission (as all default apps do):

image

This is possibly something wrong with my implementation. My settings do not specify the email scope since I assume it is included by default.

Incompatibility between Loginpass/FastAPI and Authlib

In Loginpass there is a common function used to normalize user information. For example, https://github.com/authlib/loginpass/blob/master/loginpass/twitter.py#L17 Notice that it is not an async function:
def normalize_userinfo(client, data):

However, there is an attempt to call it as async in https://github.com/lepture/authlib/blob/master/authlib/integrations/base_client/async_app.py#L142

The incompatibility triggers an error.

What is the best way to address this? Currently I'm removing the await for my own purpose but I don't know the impact of this

Azure issuer validation fails for multitenant apps

Using a version from github (so it's got PR #17 included) I had to add a second issuer to list of issuers before I could use loginpass with the AAD v2 endpoint. I validate against the common endpoint in my multitenant app, but the token I get back is iss":"https://login.microsoftonline.com/17b0504e-32de-rest-of-guid/v2.0"

This was my change to azure.py - obviously hard-coding the app in the library is very, very hacky,

def create_azure_backend(name, tenant, version=2):
    if version == 1:
        authorize_url = '{}{}/oauth2/authorize'.format(_BASE_URL, tenant)
        token_url = '{}{}/oauth2/token'.format(_BASE_URL, tenant)
        issuer_url = 'https://sts.windows.net/{}/'.format(tenant)
    elif version == 2:
        authorize_url = '{}{}/oauth2/v2.0/authorize'.format(_BASE_URL, tenant)
        token_url = '{}{}/oauth2/v2.0/token'.format(_BASE_URL, tenant)
        issuer_url = '{}{}/v2.0'.format(_BASE_URL, tenant)
    else:
        raise ValueError('Invalid version')

    local_issuer_url = '{}{}/v2.0'.format(_BASE_URL, '17b0504e-32de-rest-of-guid')
    claims_options = {
        "iss": {
            "values": [issuer_url, local_issuer_url]
        }
    }

Loginpass + Azure - claim validation error for claim "iss"

What I did?

Installed loginpass using pipenv. Used the example to pull in Google and Azure provider. Tried to log in using Azure provider.

The authorization is below, it is incredibly simple. The rest is boilerplate loginpass code.

def handle_authorization(remote, token, user_info):
    printer.pprint(user_info)
    return user_info

What should happen?

The flow should complete and I should get the user_info dumped to the screen.

What actually happened?

An error authlib.jose.errors.InvalidClaimError: invalid_claim: Invalid claim "iss" is thrown and login does not proceed.

What I think is the root cause

I think that loginpass is actually using OpenID Connect to get the metadata from the server for Azure, which returns the default iss claim as a template with the {tenant} placeholder that needs to be replaced with a GUID, in my case a static one (as per https://docs.microsoft.com/en-us/azure/active-directory/develop/id-tokens). Looking at loginpass code, specifically azure.py at line 30, this should work, but I'm still getting the error.

Suddenly getting this error on loginpass.

Traceback (most recent call last):
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2463, in __call__
web_1       |     return self.wsgi_app(environ, start_response)
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2449, in wsgi_app
web_1       |     response = self.handle_exception(e)
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask_cors/extension.py", line 161, in wrapped_function
web_1       |     return cors_after_request(app.make_response(f(*args, **kwargs)))
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1866, in handle_exception
web_1       |     reraise(exc_type, exc_value, tb)
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
web_1       |     raise value
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
web_1       |     response = self.full_dispatch_request()
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1951, in full_dispatch_request
web_1       |     rv = self.handle_user_exception(e)
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask_cors/extension.py", line 161, in wrapped_function
web_1       |     return cors_after_request(app.make_response(f(*args, **kwargs)))
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1820, in handle_user_exception
web_1       |     reraise(exc_type, exc_value, tb)
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
web_1       |     raise value
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1949, in full_dispatch_request
web_1       |     rv = self.dispatch_request()
web_1       |   File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1935, in dispatch_request
web_1       |     return self.view_functions[rule.endpoint](**req.view_args)
web_1       |   File "/usr/local/lib/python3.6/site-packages/loginpass/_flask.py", line 47, in auth
web_1       |     token = remote.authorize_access_token()
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/flask/client/oauth.py", line 261, in authorize_access_token
web_1       |     token = self.fetch_access_token(redirect_uri, request_token, **params)
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/client/oauth_client.py", line 174, in fetch_access_token
web_1       |     token = session.fetch_access_token(token_endpoint, **kwargs)
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/client/oauth2_session.py", line 97, in fetch_access_token
web_1       |     return self.fetch_token(url, **kwargs)
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/oauth2/client.py", line 167, in fetch_token
web_1       |     headers=headers, **session_kwargs
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/oauth2/client.py", line 184, in _fetch_token
web_1       |     return self.parse_response_token(resp.json())
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/oauth2/client.py", line 305, in parse_response_token
web_1       |     self.handle_error(error, description)
web_1       |   File "/usr/local/lib/python3.6/site-packages/authlib/client/oauth2_session.py", line 109, in handle_error
web_1       |     raise OAuthError(error_type, error_description)
web_1       | authlib.common.errors.AuthlibBaseError: invalid_request: Missing parameter: redirect_uri

So as I said everything was working and suddenly now I get this error, I am honestly not sure what could be wrong.

loginpass connection classes aren't automatically initialized with their configuration

It makes sense for the various connection classes to be usable on their own:

from loginpass.google import Google
oauth_client = Google(
        GOOGLE_KEY,
        GOOGLE_SECRET)
# Do stuff with the client

But it this OAuth client isn't automatically initialized with the URLs and scopes defined in OAUTH_CONFIG, so it's defeats the purpose of these special classes. Their purpose is to contain this custom configuration.

Would it make more sense to initialize them? Here's a diff against loginpass 0.2.1:

diff --git a/loginpass/_core.py b/loginpass/_core.py
index 61257aa..e9b4055 100644
--- a/loginpass/_core.py
+++ b/loginpass/_core.py
@@ -7,9 +7,15 @@ class OAuthBackend(OAuthClient):
     """Backend for OAuth Registry"""
     OAUTH_TYPE = None
     OAUTH_NAME = None
-    OAUTH_CONFIG = None
+    OAUTH_CONFIG = {}
     JWK_SET_URL = None
 
+    def __init__(self, *args, **kwargs):
+        for k, v in self.OAUTH_CONFIG.items():
+            if k not in kwargs:
+                kwargs[k] = v
+        OAuthClient.__init__(self, *args, **kwargs)
+
     def fetch_jwk_set(self, force=False):
         if not self.JWK_SET_URL:
             return None

RuntimeError: 'Missing "jwks_uri" in metadata'

I tried using the exact code from loginpass/flask_example and got the following errors:

[2020-02-21 18:32:38,624] ERROR in app: Exception on /google/auth [GET]
Traceback (most recent call last):
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/loginpass/_flask.py", line 60, in auth
    user_info = remote.parse_openid(token, nonce)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/loginpass/google.py", line 50, in parse_openid
    token.get('access_token'), nonce
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/loginpass/_core.py", line 115, in parse_id_token
    claims_params=claims_params,
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/authlib/jose/rfc7519/jwt.py", line 121, in decode
    data = self._jws.deserialize_compact(s, key_func, decode_payload)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/authlib/jose/rfc7515/jws.py", line 115, in deserialize_compact
    self._algorithms, jws_header, payload, key)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/authlib/jose/util.py", line 8, in prepare_algorithm_key
    key = key(header, payload)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/authlib/jose/jwk.py", line 27, in key_func
    return load_key(key, header, payload)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/authlib/jose/jwk.py", line 21, in load_key
    key = key(header, payload)
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/loginpass/_core.py", line 95, in load_key
    jwk_set = remote.fetch_jwk_set()
  File "/Users/max/.pyenv/versions/anaconda3-2019.10/lib/python3.7/site-packages/authlib/integrations/base_client/remote_app.py", line 137, in fetch_jwk_set
    raise RuntimeError('Missing "jwks_uri" in metadata')
RuntimeError: Missing "jwks_uri" in metadata

Is this a problem with the library? My creds were correct, I believe (and I set the google oauth API to allow redirects to localhost, etc.).

AuthlibBaseError not caught in Flask blueprint

We are using the Flask blueprint to enable OAuth sign-on with Github. For reasons not yet understood, Github stopped accepting our client_secret. This caused authlib to raise an AuthlibBaseError when the flask blueprint code called remote.authorize_access_token(). As the blueprint code doesn't handle this error, a 500 was returned. I would prefer that this error be caught and passed into the handle_authorize function, so that we can react to the problem. If this seems reasonable, I'd be happy to take a stab at a PR.

We are running loginpass 0.2.1 with authlib 0.10 and flask 1.0.2 on Python 3.7. The relevant part of the traceback follows:

  File "/env/lib/python3.7/site-packages/loginpass/_flask.py", line 47, in auth
    token = remote.authorize_access_token()
  File "/env/lib/python3.7/site-packages/authlib/flask/client/oauth.py", line 261, in authorize_access_token
    token = self.fetch_access_token(redirect_uri, request_token, **params)
  File "/env/lib/python3.7/site-packages/authlib/client/client.py", line 174, in fetch_access_token
    token = session.fetch_access_token(token_endpoint, **kwargs)
  File "/env/lib/python3.7/site-packages/authlib/client/oauth2_session.py", line 203, in fetch_access_token
    return self._parse_and_validate_token(resp.json())
  File "/env/lib/python3.7/site-packages/authlib/client/oauth2_session.py", line 333, in _parse_and_validate_token
    raise OAuthError(error=error, description=description)
authlib.common.errors.AuthlibBaseError: incorrect_client_credentials: The client_id and/or client_secret passed are incorrect."  

local url when registering blueprint

I'm using loginpass with flask inside a docker container.

application.register_blueprint(bp, url_prefix='/oauth/{}'.format(backend.OAUTH_NAME))

when accessing to:

https://-my-public-address-/oauth/google/login

it gives the next message:

Invalid parameter value for redirect_uri: Non-public domains not allowed: http://-docker-url-:8000/oauth/google/auth

Is there any option to send my public address instead of the docker one for the correct redirection?

Azure's userinfo endpoint not working in v2

When using v2 of the Azure API, in profile() it tries to call /openid/userinfo.

However, according to the passport-azure-ad node package (sorry, this is the best documentation I could find) userinfo does not work with v2, and one needs to extract that information from id_token.

Cannot import create_flask_blueprint on Authlib 0.14.3

Copying and pasting the example in the README into app.py, I run into an ImportError from Authlib:

> poetry run flask run
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Usage: flask run [OPTIONS]

Error: While importing "app", an ImportError was raised:

Traceback (most recent call last):
  File "/Users/lucas/Library/Caches/pypoetry/virtualenvs/authlib-flask-2JP271i9-py3.8/lib/python3.8/site-packages/flask/cli.py", line 240, in locate_app
    __import__(module_name)
  File "/Users/lucas/revolution-software/authlib-flask/app.py", line 3, in <module>
    from loginpass import create_flask_blueprint
  File "/Users/lucas/Library/Caches/pypoetry/virtualenvs/authlib-flask-2JP271i9-py3.8/lib/python3.8/site-packages/loginpass/__init__.py", line 1, in <module>
    from ._core import register_to
  File "/Users/lucas/Library/Caches/pypoetry/virtualenvs/authlib-flask-2JP271i9-py3.8/lib/python3.8/site-packages/loginpass/_core.py", line 1, in <module>
    from authlib.client import OAuthClient
  File "/Users/lucas/Library/Caches/pypoetry/virtualenvs/authlib-flask-2JP271i9-py3.8/lib/python3.8/site-packages/authlib/client/__init__.py", line 9, in <module>
    from .oauth_client import OAuthClient, OAUTH_CLIENT_PARAMS
  File "/Users/lucas/Library/Caches/pypoetry/virtualenvs/authlib-flask-2JP271i9-py3.8/lib/python3.8/site-packages/authlib/client/oauth_client.py", line 1, in <module>
    from authlib.integrations._client import RemoteApp as OAuthClient
ModuleNotFoundError: No module named 'authlib.integrations._client'

The README says "It works well with Authlib v0.14.3+.", but I am using Authlib 0.14.3 and it doesn't seem to be compatible:

> poetry run pip freeze
Authlib==0.14.3
certifi==2020.4.5.2
cffi==1.14.0
chardet==3.0.4
click==7.1.2
cryptography==2.9.2
Flask==1.1.2
idna==2.9
itsdangerous==1.1.0
Jinja2==2.11.2
loginpass==0.4
MarkupSafe==1.1.1
pycparser==2.20
requests==2.23.0
six==1.15.0
urllib3==1.25.9
Werkzeug==1.0.1

Inspecting the master branch of Authlib, it noticed that authlib.client has been removed, so it doesn't seem that using the latest development version of Authlib would fix this issue.

Have I done something wrong, or is the README incorrect? If the README is incorrect, which version(s) of Authlib are compatible with loginpass?

How do you pass a dictionary to _AUTHORIZE_PARAMS

Consider https://github.com/authlib/loginpass/blob/master/loginpass/_fastapi.py#L81

I'm trying to generate a refresh token for Google and it appears I need to set access_type: offline in the authorization url. To be honest, it feels like its black magic to get the refresh token.

Anyways, I don't know how to set a dictionary to the GOOGLE_AUTHORIZE_PARAMS when this value is coming from the config, which is an environment variable.

@zaghaghi Have you tried using your PR to generate a refresh token? Any help here would be much appreciated. Thanks!

How to use azure provider

Nevermind, it seems pip install does not pull down the latest version. The latest azure.py in the master branch fixes the problem.

In flask_example/config.example.py, the entries for AZURE is missing.

I added client id and client secret like other entries, I got error below. Maybe I need to provide tenant ID? If I do, how to name that in the config.py file?
Thanks

Timestamp: 2021-06-04 17:07:28Z
Traceback (most recent call last):
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2088, in call
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python3.8/site-packages/werkzeug/middleware/dispatcher.py", line 78, in call
return app(environ, start_response)
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2088, in call
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2073, in wsgi_app
response = self.handle_exception(e)
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/usr/local/lib/python3.8/site-packages/loginpass/_flask.py", line 62, in auth
user_info = remote.parse_id_token(token)
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/flask_client/remote_app.py", line 81, in parse_id_token
return self._parse_id_token(flask_req, token, claims_options, leeway)
File "/usr/local/lib/python3.8/site-packages/authlib/integrations/base_client/remote_app.py", line 203, in _parse_id_token
claims.validate(leeway=leeway)
File "/usr/local/lib/python3.8/site-packages/authlib/oidc/core/claims.py", line 35, in validate
self.validate_iss()
File "/usr/local/lib/python3.8/site-packages/authlib/jose/rfc7519/claims.py", line 109, in validate_iss
self._validate_claim_value('iss')
File "/usr/local/lib/python3.8/site-packages/authlib/jose/rfc7519/claims.py", line 71, in _validate_claim_value
raise InvalidClaimError(claim_name)
authlib.jose.errors.InvalidClaimError: invalid_claim: Invalid claim "iss"

Support for authlib 1.0.0

Flask is currently broken since a fresh install will install authlib 1.0.0.

python3.10/site-packages/loginpass/_flask.py:83: in register_to
    from authlib.integrations.flask_client import FlaskRemoteApp
E   ImportError: cannot import name 'FlaskRemoteApp' from 'authlib.integrations.flask_client' (python3.10/site-packages/authlib/integrations/flask_client/__init__.py)

Github OAuth with non public email

The UserInfo object does not contain the email when requesting a non-public email with {'scope': 'user:email'}. As a workaround, it is suggested to get the email with the token that was received see - https://stackoverflow.com/questions/35373995/github-user-email-is-null-despite-useremail-scope

A possible implementation would extend GitHub.profile with self.get("user/emails") and use whatever is returned there in case the initial email is None. Would you accept PR on this?

OAUTH_BACKENDS missing linkedin

OAUTH_BACKENDS in __init.py is missing LinkedIn

OAUTH_BACKENDS = [ BattleNet, Twitter, Facebook, Google, GitHub, Dropbox, Instagram, Reddit, Gitlab, Slack, Discord, StackOverflow, Bitbucket, Strava, Spotify, Yandex, Twitch, VK ]

_google:nonce vs _google_authlib_nonce_

This issue arose in Python 3.7, and was reproducible on OSX and Windows. . When using loginpass with Flask and the Google backend, my session ends up with both "_google:nonce" and "google_authlib_nonce". When validating the nonce from a returned token, authlib/loginpass is comparing _google:nonce to the nonce returned in the token. This results in: authlib.jose.errors.InvalidClaimError: invalid_claim: Invalid claim "nonce"

If I run the debugger and replace the nonce value (session["_google:nonce"]) with session["google_authlib_nonce"], then everything validates fine.

I believe this may be an incompatibility between the versions of authlib and loginpass bestowed upon me by pip.

Steps to reproduce:

Edit: looks like this is addressed by #55, but maybe still an issue since limiting the version of authlib probably isn't ideal?

Azure AD B2C Provider

Azure AD B2C supports OpenID Connect and would be a good enterprise quality service to add to Authlib loginpass.

I don't understand this library enough to add this, but I can help with some of the base information and I'm vry happy to help with live testing.

Here's a example from a live tenant configuration URL

f'https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/v2.0/.well-known/openid-configuration?p={policy}'

Response

{
  f"issuer": "https://{tenant}.b2clogin.com/{SOME_GUIDE}/v2.0/",
  f"authorization_endpoint": "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/oauth2/v2.0/authorize?p={policy}",
  f"token_endpoint": "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/oauth2/v2.0/token?p={policy}",
  f"end_session_endpoint": "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/oauth2/v2.0/logout?p={policy}",
  f"jwks_uri": "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/discovery/v2.0/keys?p={policy}",
  "response_modes_supported": [
    "query",
    "fragment",
    "form_post"
  ],
  "response_types_supported": [
    "code",
    "code id_token",
    "code token",
    "code id_token token",
    "id_token",
    "id_token token",
    "token",
    "token id_token"
  ],
  "scopes_supported": [
    "openid"
  ],
  "subject_types_supported": [
    "pairwise"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "client_secret_basic"
  ],
  "claims_supported": [
    "emails",
    "newUser",
    "oid",
    "sub",
    "idp",
    "extension_customUserAttribute",
    "tfp",
    "iss",
    "iat",
    "exp",
    "aud",
    "acr",
    "nonce",
    "auth_time"
  ]
}

Some notes:

The host URL uses tenant more than once:

    host = 'https://{}.b2clogin.com/{}.onmicrosoft.com/'.format(tenant, tenant)
    authorize_url = '{}oauth2/v2.0/authorize'.format(host)
    token_url = '{}oauth2/v2.0/token'.format(host)
   jwk_set__url = '{}discovery/v2.0/keys'.format(host)
   iss _url = https://{}.b2clogin.com/__SOME_GUID__/v2.0/'.format(tenant)  <-- Note the GUID, not sure where that value originates from

Sign In, Sign Up, etc require more kwargs:

client_kwargs': {
    'client_id': client_id,
    'nonce': '??',
    'p': policy,
    'response_type': 'id_token',
    'scope': 'openid offline_access',
},

I show nonce as required, but its unclear to me if the blueprints are handling that.

If I can help in any other way, please ask.

Clarify LICENSE

The open source license in the repo is very clearly AGPLv3 whereas the link in the readme says:

Loginpass is a group member of Authlib, it is licensed under AGPLv3+. Authlib commercial license applies to this project too, you can get a commercial license at Authlib Commercial Plans.

The page at which says that open source products fall under the BSD license. I take it that this means authlib itself falls under BSD, but loginpass is AGPL?

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.