Coder Social home page Coder Social logo

flask-pyoidc's People

Contributors

csfreak avatar gdestuynder avatar gramthanos avatar h4ckd0tm3 avatar infohash avatar josteinl avatar rebeckag avatar rgmz avatar stevenmirabito avatar thorekr avatar titotix avatar zamzterz 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

Watchers

 avatar  avatar  avatar  avatar  avatar

flask-pyoidc's Issues

Request: Release of minor version

Hi!

As the change introduced in #32 fixes nasty error-messages when logging out against keycloak instances, I wanted to kindly ask if you might push a new minor version to pypy which includes commit ea97af2. Then all dependent projects could easily upgrade to work with keycloak.

Thank you and kind regards

Jörn

KeyError: 'end_session_state' on logout

Hi, I received the following error on logout. It happened only two times, so I'm not sure what's behind it, I cannot reproduce it intentionally so I can't provide more details I'm afraid.
Looking at the code, it seems like default value for pop should suffice. However, I understand that it may be just a workaround and actual reason behind the error is unknown 🤷
It was also mentioned in #88

File "//enkeili/viirtualenv/lib/python3.8/site-packages/flask_pyoidc/flask_pyoidc.py", line 256, in wrapper
  if flask.request.args['state'] != flask.session.pop('end_session_state'):
File "/enkeili/virtualenv/lib/python3.8/site-packages/werkzeug/datastructures.py", line 269, in pop
  rv = super(UpdateDictMixin, self).pop(key)
KeyError: 'end_session_state'

Getting authentication token when using cilogon

Hi,

I'm trying to use this library to authenticate with cilogon(https://www.cilogon.org/oidc). I'm able to authenticate and get the response which contains code and state in the query string. After getting that, I need to request for authentication token.

curl -d grant_type=authorization_code
-d client_id=$CILOGON_CLIENT_ID
-d client_secret=$CILOGON_CLIENT_SECRET
-d code=$CILOGON_CODE
-d redirect_uri=$CILOGON_REDIRECT_URI
https://cilogon.org/oauth2/token \

cilogon-token-response.txt

I could not find a way to get the token from this library. I tried to use pyoidc (https://pyoidc.readthedocs.io/en/latest/examples/rp.html) to get the token using client.do_access_token_request() method which gives me error saying "oic.oauth2.exception.GrantError: No grant found for state:'t9N04NZFIlwtZlHl'".

Could you please help me how to get the authentication token?

Silently refreshing tokens fails at redirect with empty UserSession

We're using Flask-pyoidc to protect our API-Endpoints and SwaggerUI from unwanted accesses.
What currently happens, when session_refresh_interval_seconds is set, is that API requests using SwaggerUI fail when running into the refresh interval timeout.

What happens is that, when issuing a request to the API endpoint using Swagger/Flasgger, the request is redirected to our IDP and redirected back to our service's redirect_uri.
When this redirect back to us happens (first an OPTIONS request, assuming it's a preflight, which then turns into a GET) it is without data in the UserSession, resulting in Trying to pick-up uninitialised session without specifying 'provider_name' in _handle_authentication_response.

To work around this currently one has to switch to a different tab and loading the SwaggerUI there again to refresh the token.

problem when behind https terminating proxy

Hello,

I'm running flask-pyoidc behind a https terminating proxy. The app I have works otherwise well, but after login, it issues a redirect to itself as http.

I think the issue is here:

flask.session['destination'] = flask.request.url

so a request full url is stored (but it's http, as https is terminated elsewhere), and then the redirect back is issued after authentication here:

destination = flask.session.pop('destination')
return redirect(destination)

I have no idea I could convince flask-pyoidc to issue a https redirect here with configuration only, please help?

Otherwise, I think it could be fixed by storing a relative url (i.e. just a path + query parameters). Then whatever configuration present maybe can help flask issue a correct redirect with https. But I have not investigated this option, it's just a thought.

How to change redirecturi for static client registration?

I want to authenticate with google cloud application. I have created an application in google cloud and created client_credentials as well. my authentication works perfectly only when I set redirect_uri in google application client credentials to <application_url>/redirect_uri. I want to change the redirect uri to some other. I'm facing issues to change the redirect_uri from my application. How to achieve this?

cannot find a way how to authenticate POST request

Hi, I am having problems with authentication of POST requests using flask-pyoidc. I have got such code snippet:

@app.route("/send", methods = ['POST'])
@auth.oidc_auth("default")
def process_submit():

but the authetication request is sent to server by GET method and server then redirects me also with GET method. How should I proceed please?

Unable to use decorators when splitting app in multiple scripts

I have a (very) small Flask app that I am trying to structure in multiple files/modules.

I believe I have initialized correctly the auth provider:

...

from app.admin.views.my_home import MyHomeView
from app.auth import bp as auth_bp

...

from config import Config

db = MongoEngine()
mail = Mail()
auth = OIDCAuthentication({"default": get_oidc_config()})


def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)

    with app.app_context():
        db.init_app(app)
        mail.init_app(app)
        auth.init_app(app)

        #  init blueprints
        app.register_blueprint(auth_bp, url_prefix="/auth")

        # create admin dashboard and custom view
        admin = flask_admin.Admin(
            app=app, index_view=MyHomeView(name="Home", url="/admin")
        )
        admin.add_view(IssuerInviteView(IssuerInvite))

    return app

The configuration object:

def get_oidc_config():
    return ProviderConfiguration(
        issuer=Config.OIDC_ISSUER,
        client_metadata=ClientMetadata(
            client_id=Config.OIDC_CLIENT,
            client_secret=Config.OIDC_SECRET,
            post_logout_redirect_uris=[Config.OIDC_POST_LOGOUT_REDIRECT_URI],
        ),

The blueprint registers the following views:

from flask import redirect, render_template, url_for

from app import auth
from app.auth import bp


@bp.route("/logout")
@auth.oidc_logout
def logout():
    return redirect(url_for("admin.index"))


@bp.route("/unauthorized")
@auth.oidc_logout
def unauthorized():
    return render_template("auth/unauthorized.html")

And MyHomeView class looks like this:

from flask import redirect, session, url_for
from flask_admin import AdminIndexView, expose
from flask_pyoidc.user_session import UserSession

from app import auth
from config import Config


class MyHomeView(AdminIndexView):
    @expose("/")
    @auth.oidc_auth("default")
    def index(self):
        user_session = UserSession(session)
        client_name = Config.OIDC_CLIENT
        if (
            client_name in user_session.id_token
            and "admin" in user_session.id_token[client_name]["roles"]
        ):
            return super(MyHomeView, self).index()
        else:
            return redirect(url_for("auth.unauthorized"))

When I try to hit any of the exposed endpoints (/admin, /logout, /unauthorized) I get the following error:

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/app/server.py", line 1, in <module>
    from app import create_app
  File "/app/app/__init__.py", line 8, in <module>
    from app.admin.views.my_home import MyHomeView
  File "/app/app/admin/views/my_home.py", line 5, in <module>
    from app import auth
  File "/app/app/auth/__init__.py", line 5, in <module>
    from app.auth import routes
  File "/app/app/auth/routes.py", line 8, in <module>
    @auth.oidc_logout
AttributeError: module 'app.auth' has no attribute 'oidc_logout'

If I move all the components into the same script file everything works as expected (see this), but I'd like to be able to structure things a bit to avoid having a huge main script.

Any idea what I am doing wrong? Current repo structure for reference visible here.

Shibboleth IDP with OIDC plugin says "Unrecognized client"

Hi, A very simple Flask-pyoidc client here, connected to a Shibboleth OIDC Idp (Shibboleth with OIDC plugin).
The auth decorator works and redirect to the login page, but then, after login, we always get

Something went wrong with the authentication, please try to login again.

Is it related to this post

The problem was, in fact, that they weren't including the HTTP authentication header to do HTTP basic auth. They added this, and it fixed the problem. Those for the post endpoint information, though. That could come in handy in the future.

Thanks for your help. Have a nice day.

In the logs of Idp

2020-11-23 14:38:19,281 - 212.47.237.47 - WARN [org.geant.idpextension.oidc.profile.impl.ValidateEndpointAuthentication:203] - Profile Action ValidateEndpointAuthentication: Unrecognized client authentication com.nimbusds.oauth2.sdk.auth.ClientSecretBasic@15d10d97 for client_secret_post

My code (I checked every var in capital letters)

pmd = ProviderMetadata(
    issuer=app.config['OIDC_ISSUER'],
    authorization_endpoint=app.config['OIDC_AUTH_URI'],
    token_endpoint=app.config['OIDC_TOKEN_URI'],
    userinfo_endpoint=app.config['OIDC_USERINFO_URI']
)

pc = ProviderConfiguration(
    issuer=app.config['OIDC_ISSUER'],
    # provider_metadata=pmd,
    userinfo_http_method=app.config['OIDC_USERINFO_HTTP_METHOD'],
    client_metadata=ClientMetadata(
        client_id=app.config['OIDC_CLIENT_ID'],
        client_secret=app.config['OIDC_CLIENT_SECRET']
    ),
    auth_request_params={
        'scope': app.config['OIDC_SCOPES']
    }
)

auth = OIDCAuthentication({'default': pc}, app)

Decorator for validating token

Hello,

I would like to know if there is a separate decorator for validating the token?
or is the use of @auth.oidc_auth dual purpose?

Login and logout with browser works perfectly however, I am not able to test the API using Postman.
Steps

Logged in (using browser), received the token
Copied the token to Postman 'Auth' to be used as a 'Bearer'
Called a resource (API) with '@auth.oidc_auth(default)' decorator - Postman returned the HTML page as it requires the login process again (probably discarded the access token)
Please let me know.

Thank you.

e-mail scope by default?

Hi @zamzterz!

Testing Flask-pyoidc and want to collect the users e-mail from google OIDC provider (for local account registration/attributes etc).

This works when modifying line 83 of pyoidc_facade.py to read:

'scope': ['openid', 'email'],

"logged in" response then successfully includes e-mail in user_session.userinfo.

Is this the right place to add new scopes? Is there a reason this wasn't added as default?
By no means an expert with OIDC.

Thanks,
M

Disable @auth decorator when testing/developing

Are there any recommendations for disableing oidc for testing? Like setting an environment variable for the development env.

When I run my existing unittests for Flask, the authentication service gets called.

I could put a if-statement around some of my code, but I would rather not modify the @auth-decorator.

Logout with Blueprints

I have a Flask Application Factory and I use blueprints to structure my Application. The login works fine, but I have problems implementing the logout.

@main.route('/logout_user') @auth.oidc_logout def logout_function(): return "You've been successfully logged out!"

When i call /logout_user i get this error:
werkzeug.routing.BuildError: Could not build url for endpoint 'logout_function'. Did you mean 'main.logout_function' instead?

Theoretically I should pass 'main.logout_function' somehow to 'oidc_logout' so it knows which function it should use. But I don't have a clue on how to do that. Any idea on how to solve this problem is greatly appreciated.

Thanks

'unsolicited_response' 'No authentication request stored.'

Hi all.
Seems that I have hard time understanding how to use the redirect_url.

I have this:

@api_default.route('/oidc/login')
@oidc_auth.oidc_auth(PROVIDER_NAME1)
def oidc_login():
    return redirect('/redirect_uri')

@oidc_auth.error_view
def error(error=None, error_description=None):
    return jsonify({'error': error, 'message': error_description})


@api_default.route('/redirect_uri')
def oidc_redirect_uri():
    logging.info("You are redirected")
    return api_default.send_static_file('index.html')

At first I thought that redirect url should be uri where the Open ID provider sends us automatically after successful login. On my side, that never happened, I have configured redirect_url with OIDC_REDIRECT_URI = 'http://localhost:8000/redirect_uri' but the Open ID provider never returns to it. I checked the Open ID provider app settings and I see that the redirect_uri is set correctly. Thus I cannot understand why after athentication to the Open ID provider is not going to the redirect uri and finally I concluded that I have to redirect to the redirect_uri (and this is not provider job, or may be I am wrong)
So after /oidc/login
successful athentication
I redirect to return redirect('/redirect_uri')
then the error endopoint shows this error from the title
'unsolicited_response' 'No authentication request stored.'
then it loads the localhost:8000/redirect_uri in browser
but the redirect_uri is not invoked.

I am confused. Please take a look if you have time.
Regards

Authentication fails with "KeyError: 'state'"

Traceback:

  File "/home/enkelli/virtualenv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/enkelli/virtualenv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/enkelli/virtualenv/lib/python3.8/site-packages/flask_pyoidc/flask_pyoidc.py", line 145, in _handle_authentication_response
    flask.session.pop('state'),
  File "/home/enkelli/virtualenv/lib/python3.8/site-packages/werkzeug/datastructures.py", line 269, in pop
    rv = super(UpdateDictMixin, self).pop(key)
KeyError: 'state'

Unfortunatelly, I cannot provide steps how to reproduce this error. It happened only a few times, often when I went to my website after a longer period of inactivity (much more than session refresh interval but less than cookie expiration time).

In this code where error happens is simple dictionary pop(). I wonder what would happen if we prevent KeyError passing default (None) value there, i.e. flask.session.pop('state', None). It would prevent the KeyError. However, this may be just a nasty hack and I do not know what will follow with state = None. Another option I think of - detect this situation (missing state) and raise custom flask_pyoidc authentication error which would end in @auth.error_view.

Issues refreshing tokens when 'session_refresh_interval_seconds' is set

With the current state of flask_pyoidc we're running into the issue of not silently refreshing the tokens when it's desired.

Our current setup is as follows: API endpoints are protected using flask_pyoidc, with the session_refresh_interval_seconds providing prompt=none refreshing after 5 minutes.

The first issue we encountered was running into the silent refreshing when the initial authentication has not been successfully established yet.
The second issue was the refreshing being triggered based on the previous auth_time, which didn't change with the request. Instead, what worked was taking the expiry time provided by the OIDC instance to trigger the silent refresh that way.

To illustrate a proposed way of fixing it we've created this change (this is not PR-quality code by any means):
fredldotme@8a062a1

This forces the refresh after 5 minutes. Note that we've added 20 seconds of headroom before the expiry time runs out.

I'd like to get your suggestions on this topic and maybe a way forward in the form of code being merged into upstream.

No initialised user session error first time when user consent page is shown

When the user logins for the first time, my auth provider shows the user consent page and on the redirect after consent page ,the "No initialised user session error " is thrown.
in below two scenarios, 1st one failed and 2nd was successful.

  1. home -> authorize -> successful login -> consent page -> redirect url -> No initialised user session error

  2. home -> authorize -> successful login -> redirect url -> token verification -> success

Any thoughts?

Do I Need both provider and client?

Sorry if this is a basic concept. I'm a bit confused as to how to use this library as only a client that is trying to authenticate with a provider. In your notes under 'Usage', your bullet points suggest how to create a OIDCAuthentication object. Two of them are for provider and one for client. So I'm wondering if I need to create a provider object as well as a client object. In the example app in app.py, you created only one object which looks like one of the options of the provider object creation. Am I supposed to create another OIDCAuthentication for the client? If so, how do the two interact?

If I have to create the client registration, where do I get the redirect_uris you mention in the description?

As it stands right now, my in house provider has given me a client_id, client_secret, token and auth endpoints. How do I use these parameters and get my simple 'hello world' app authenticate with provider? Sorry for my limited understanding.

Error responses using undefined _redirect_uri_endpoint attribute

In some cases (e.g. when the auth state goes stale after much time) we rightfully receive an error that should be passed on to the client

Exception on /redirect_uri [POST]

Traceback (most recent call last):

  File "../flask_pyoidc/flask_pyoidc.py", line 156, in _handle_authentication_response

    flask.session.pop('nonce'))

  File "../lib/python3.7/site-packages/flask_pyoidc/auth_response_handler.py", line 60, in process_auth_response

    raise AuthResponseErrorResponseError(auth_response.to_dict())

flask_pyoidc.auth_response_handler.AuthResponseErrorResponseError: {'error': 'login_required', 'state': 'm92iSPHP7qMBXSkQ'}

 

During handling of the above exception, another exception occurred:

 

Traceback (most recent call last):

  File "../lib/python3.7/site-packages/flask/app.py", line 2447, in wsgi_app

    response = self.full_dispatch_request()

  File "../lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request

    rv = self.handle_user_exception(e)

  File "../lib/python3.7/site-packages/flask_cors/extension.py", line 165, in wrapped_function

    return cors_after_request(app.make_response(f(*args, **kwargs)))

  File "../lib/python3.7/site-packages/flask_cors/extension.py", line 165, in wrapped_function

    return cors_after_request(app.make_response(f(*args, **kwargs)))

  File "../lib/python3.7/site-packages/flask/app.py", line 1821, in handle_user_exception

    reraise(exc_type, exc_value, tb)

  File "../lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise

    raise value

  File "../lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request

    rv = self.dispatch_request()

  File "../lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request

    return self.view_functions[rule.endpoint](**req.view_args)

  File "../lib/python3.7/site-packages/flask_pyoidc/flask_pyoidc.py", line 158, in _handle_authentication_response

    return self._handle_error_response(e.error_response, is_processing_fragment_encoded_response)

  File "../lib/python3.7/site-packages/flask_pyoidc/flask_pyoidc.py", line 186, in _handle_error_response

    return '/' + self._redirect_uri_endpoint + '?error=1'

AttributeError: 'OIDCAuthentication' object has no attribute '_redirect_uri_endpoint'

Unfortunately it looks like the error handler tries to return a nonexistant attribute (holdover from old code?) for the redirect uri endpoint. I'm guessing this should be
self._redirect_uri_config.endpoint instead.

login required occasional error

Hi, I have got a problem that is occuring when I reboot my computer or sometimes when I relaunch my browser (google chrome). When I try to reach authenticated website on my running server I got "Something when wrong..." and in logs I see:
ERROR:flask_pyoidc.flask_pyoidc:{"error": "login_required", "state": "xxx"}
When I manualy go on /logout and then try it again I can sing in and everything is ok. Can you please hint me what could be wrong or potentionally adjust the code so it would redirect user to authentication automatically?

How to use pyoicd with multiple blueprints

My Flask project is growing and I want to split my major blueprint up into smaller components.
Is it possible to use pyoidc for this?

When I register multiple blueprints, an exception occurs immediately:

  File "C:\Users\az27355\Miniconda3\envs\backoffice_nova\lib\site-packages\flask\app.py", line 1283, in add_url_rule
    "existing endpoint function: %s" % endpoint
AssertionError: View function mapping is overwriting an existing endpoint function: redirect_uri
# init.py
   [...]
    app = Flask(__name__, instance_relative_config=False)
    with app.app_context():
        from .main import main_routes, sub_routes, kmd_nova_routes  # import routes after initializing db object

        app.register_blueprint(main_routes.main_bp)
        app.register_blueprint(sub_routes.sub_bp)
        app.register_blueprint(kmd_nova_routes.nova_bp)

        return app
# in each blueprint
    [...]
    auth = OIDCAuthentication({PROVIDER_NAME1: PROVIDER_CONFIG})
    auth.init_app(current_app)
    [...]

Update README to show how to change Auth Request Values

It would be beneficial to add additional documentation as to how to change auth request arguments, such as scope. Since scope is defaulted to openid, it's very easy to spend a significant amount of time trying to figure out why other values are not showing up in ID/UserInfo responses that are linked to other scopes, only to realize it's a scope issue.

Currently we can do the following to change scope when setting up the Provider Configuration:

config = ProviderConfiguration([provider/client config], auth_request_params={'scope': ['openid', 'profile']})

The above scope key will get updated in code here: https://github.com/zamzterz/Flask-pyoidc/blob/master/src/flask_pyoidc/pyoidc_facade.py#L81

Not working for Implicit Flow & Hybrid Flow

Conflicting logic for the following lines, thus break the OIDC Implicit Flow & Hybrid Flow.

src/flask_pyoidc/flask_pyoidc.py LINE 137

        if 'auth_request' not in flask.session:
            return self._handle_error_response({'error': 'unsolicited_response', 'error_description': 'No authentication request stored.'})
        auth_request = AuthorizationRequest().from_json(flask.session.pop('auth_request'))

        if flask.session.pop('fragment_encoded_response', False):
            return importlib_resources.read_binary('flask_pyoidc', 'parse_fragment.html').decode('utf-8')

1st pass: GET /redirect_uri
Identity Provider redirect back with fragment encoded response.
Thus the code above pop 'auth_request' and return parse_fragment.html
Browser execute the parse_fragment JS and convert the request to POST /redirect_uri

2nd pass: POST /redirect_uri
However, 'auth_request' had already popped during 1st pass, thus return 'No authentication request stored.' error.

Possible fix is to change the order as following:

        if flask.session.pop('fragment_encoded_response', False):
            return importlib_resources.read_binary('flask_pyoidc', 'parse_fragment.html').decode('utf-8')

        if 'auth_request' not in flask.session:
            return self._handle_error_response({'error': 'unsolicited_response', 'error_description': 'No authentication request stored.'})
        auth_request = AuthorizationRequest().from_json(flask.session.pop('auth_request'))

Not to pop 'auth_request' for the 1st pass, thus can still read it during 2nd pass.

Support fixed destination URL to redirect to after successful authentication

Right now, the destination URL is taken from the request that triggered the authenticaiton workflow - which is indeed ideal in most circumstances:

flask.session['destination'] = flask.request.url

In my case, I have a React SPA frontend, which works up intil the last redirect of the authenticated user. That redirect is taking the user to an "ugly" JSON output page, rather than back to the SPA endpoint. Would be nice if I can just provide a fixed "successful authentication" URL through the provider configuration.

Please update dependencies

I've run into an issue using a project that depends on flask-pyoidc. The bug appears to be triggered by code with oic that your project depends on. I followed your link to it from your README, and the offending line from the stacktrace was no longer present in the codebase and hasn't been since March. The project also appears to have migrated to a new home here(in case you want to update the README link).

This project currently depends on oic 0.12, version 0.12 was released in September 2017, quite a bit of development has happened since with the latest release being in November 2019.

I am not sure if it has resolved the bug I encountered, I have worked around it by removing the forward slash at the end of my OIDC issuer URL.

Is my code sane? Automatically require login for all routes

I have a blueprint with 6 routes, and I want all to require a login. I am paranoid about forgetting to add the decorator @auth.oidc_auth(PROVIDER_NAME) to a route, and allowing unauthorized users in.

I wrote the following:

@main_bp.before_request
@auth.oidc_auth(PROVIDER_NAME1)
def require_login():
    pass

Thank you for building and supporting this library <3

Option to init without full redirect_uri

In some cases, specifying the full redirect URI limits the ease of generalising Flask deployments, especially when the server port and/or host isn't known until after the application is running. One example would be if the Flask app is launched through gunicorn.

Being explicit about the redirect_uri does seem to be a good general practice, but having the option to fallback to "{this_app}/{OIDC_REDIRECT_ENDPOINT}" would help in this scenario.

Currently something in the spirit of this can be accomplished by patching the PyOIDC clients on app launch using the flask base_url:

auth = OIDCAuthentication({"name": config})
app.config.update(OIDC_REDIRECT_URI="http://some-dummy-value/redirect_uri")

@app.before_first_request
def patch_redirect_uri(): # this will be patched in time for the first auth flow
    auth.clients.get("name")._redirect_uri = request.base_url+"redirect_uri" 

This could be smarter, pulling the endpoint name from app.config["OIDC_REDIRECT_ENDPOINT"], or from the dummy value above, as long as we match the Flask routes that are created during init_app

Option to not use id_token_hint on logout

I'm currently testing this flask extension with a modified example/app.py against our local Keycloak setup and it works really well (thank you!), only the logout has a hickup.

Keycloak reports a "Something went wrong: Session not active.". It turns out that Keycloak does not like the id_token_hint - if I modify flask_pyoidc.py and remove id_token_hint=id_token_jwt from EndSessionRequest the problem goes away (logout is functional as far as I can tell).

Is there / could there be an option to omit the id_token_hint?

I do not know why Keycloak does not like the id_token_hint, all things look in order. I did do some comparison to Keycloak's own logout request and found that in the header of the id_token_hint Keycloak sets:

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "somesortofcode"
}

and this extension only shows:

{
  "alg": "none"
}

Maybe if typ is added Keycloak will like the id_token_hint, but I'm really just guessing here.

SERVER_NAME requirement

Hi!

I've been playing around today with this great lib but I can't figure out one thing. According to the docs, SERVER_NAME must be set in order to the lib to work, but there are no further explanations.

In my case, using the latest version of flask from pip (1.1.2) and the version 3.2.0 of this iib, if I set SERVER_NAME to something then all the request that use a different hostname (for example kubernetes liveness and readiness probes, that uses the localhost / localip) get a 404 as an answer. If I don't set SERVER_NAME the app won't come up because of the hard requirement of the lib.

Is there some alternative than setting SERVER_NAME?

Thanks in advance!

TypeError: 'NoneType' object is not subscriptable

I'm trying to get this setup for the first time so I may be doing something wrong. I got a sample app up and running, but when I hit the index page, I get the following error:

File "C:\Users\dennis\AppData\Local\Programs\Python\Python36\lib\site-packages\flask_pyoidc\flask_pyoidc.py", line 169, in wrapper
client = self.clients[session.current_provider]
TypeError: 'NoneType' object is not subscriptable

I've double checked all of my config and been re-reading through the documentation, but I can't find any mistakes. Have you seen this before?

Thanks!

Update OIC to oic-0.11.0.1

We had a fairly important bugfix drop in oic and it would be great to get flask-pyoidc in using that.

However, there appears to be a breaking change when I test:

Traceback (most recent call last):
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/flask_pyoidc/flask_pyoidc.py", line 228, in _handle_authentication_response
    authn_method=self.client.registration_response.get('token_endpoint_auth_method', 'client_secret_basic')
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oic/__init__.py", line 653, in do_access_token_request
    authn_method, **kwargs)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oauth2/__init__.py", line 761, in do_access_token_request
    http_args=http_args, **kwargs)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oauth2/__init__.py", line 684, in request_and_return
    **kwargs)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oauth2/__init__.py", line 638, in parse_request_response
    state, **kwargs)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oauth2/__init__.py", line 565, in parse_response
    verf = resp.verify(**kwargs)
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oic/message.py", line 303, in verify
    if not idt.verify(**kwargs):
  File "/Users/akrug/workspace/sso-dashboard/env/lib/python2.7/site-packages/oic/oic/message.py", line 684, in verify
    '{} != {}'.format(kwargs['iss'], self['iss']))
IssuerMismatch: auth.mozilla.auth0.com != https://auth.mozilla.auth0.com/

Session persistence

Hi,

I would like to ask: what would be the security implications of using the default cookie-based Flask's session handling with this package? Should I additionally use something like Flask-Session for having server-side sessions when using Flask-pyoidc?

Thank you in advance.

Add init_app feature

It's customary for extensions to allow initialization of an app after construction: For example, if an app.config contains the appropriate client information, this would be a desierable API:

from flask_pyoidc import OIDCAuthentication

auth = OIDCAuthentication()

def create_app():
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)

    auth.init_app(app)
    # alternatively
    # auth.init_app(app, client_info=app.config["CLIENT_INFO"])
  
    return app

As it stands, this requires the app to be global -- a huge testability no-no.

Wrong redirect_uri when using SCRIPT_NAME

When running an app behind gunicorn on a subpath (by providing a SCRIPT_NAME) the redirect_uri is set up incorrectly.

An example:
OIDC_REDIRECT_URI=https://example.com/xyz/redirect_uri
running gunicorn with:
gunicorn -e SCRIPT_NAME=xyz "app:create_app()"

Then, trying to access protected resources there is a redirect to https://example.com/xyz/redirect_uri, which returns a 404 error. At the same time the real redirect_uri route is present at https://example.com/xyz/xyz/redirect_uri

The problem in the code is located in RedirectUriConfig._parse_redirect_uri:

  • full_uri must include a subpath (as given by SCRIPT_NAME), because gunicorn does not modify it and this is where a redirect from the OIDC server goes.
  • endpoint should not include a subpath (because gunicorn adds it), but actually it does

The problem originates from assuming that anything other than the schema and domain name in OIDC_REDIRECT_URI is an endpoint.

I found a workaround by using RedirectUriConfig._parse_legacy_config, that is by setting
OIDC_REDIRECT_DOMAIN=example.com/xyz
and running gunicorn as before. This is however: a) an overuse of the term 'domain', b) depreciated

No userinfo was found

A user is able to successfully login, however, their userinfo, access token, id token, etc is returning None.

Here my configuration

provider_info = ProviderMetadata(issuer=env.get("ISSUER"), authorization_endpoint=env.get("AUTHORIZATION_ENDPOINT"), jwks_uri=env.get("JWKS_ENDPOINT"))
client_info = ClientMetadata(env.get("CLIENT_ID"), env.get("CLIENT_SECRET"))
config = ProviderConfiguration(provider_metadata=provider_info, client_metadata=client_info, userinfo_http_method="GET", auth_request_params={'scope': ['openid', 'email']})

OIDC_SECRETKEY = env.get("OIDC_SECRETKEY")
OIDC_SECRETKEY_VAL = str(OIDC_SECRETKEY).encode('ISO-8859-1').decode('unicode-escape')

app.config.update({'OIDC_REDIRECT_URI': env.get("DOMAIN") + '/oidc_callback',
                    "SECRET_KEY": OIDC_SECRETKEY_VAL,
                    'PERMANENT_SESSION_LIFETIME': 7200})

auth = OIDCAuthentication({'default': config}, app)

and in the actual route

@app.route("/")
@auth.oidc_auth('default')
def homepage():
  user_session = UserSession(session)
  print(user_session.is_authenticated()) # True
  print(user_session.userinfo) # None
  print(user_session.access_token) # None
  print(user_session.refresh_token) # None
  print(user_session.id_token) # None

Uninitialised session error

Hi!

I followed the example app and I've been able to connect to my auth endpoint. However, after I log in with the user and password I get a 500 error and the terminal says:

File "/usr/local/lib/python3.6/site-packages/flask_pyoidc/flask_pyoidc.py", line 101, in _handle_authentication_response
    client = self.clients[UserSession(flask.session).current_provider]
  File "/usr/local/lib/python3.6/site-packages/flask_pyoidc/user_session.py", line 19, in __init__
    raise UninitialisedSession("Trying to pick-up uninitialised session without specifying 'provider_name'")

I notice that the reason for this error is that since the library is calling UserSession without the provider_name parameter the first condition fails and it raises the error.

How can I fix this?

"Please try to login again." when reloading page

We're currently experiencing issues when reloading a page with outdated tokens.
Flask-pyoidc, when returning/being redirected from the ID Provider, is trying to reauthenticate us silently.
This silent reauth seems to fail as we land on the "Something went wrong with the authentication, please try to login again." page.
The UserSession has its valid information, seeing the UserSession being decoded in the output of our service, resulting in: received authentication response: {"error": "login_required", "state": "blabla"}

The only way to get out of this state is to clear the cookies and reload the page.

Can't use two auth realms

I'm trying to create a project where we have two different OIDC instances that users could log in from. The idea was to just instanciate OIDCAuthentication twice for each of the instances like below:

auth = OIDCAuthentication(app, issuer=app.config["OIDC_ISSUER"], client_registration_info={
    "client_id": app.config["OIDC_CLIENT_ID"],
    "client_secret": app.config["OIDC_CLIENT_SECRET"]
})

intro_auth = OIDCAuthentication(app, issuer=app.config["INTRO_OIDC_ISSUER"], client_registration_info={
    "client_id": app.config["INTRO_OIDC_CLIENT_ID"],
    "client_secret": app.config["INTRO_OIDC_CLIENT_SECRET"]
})

However when I try to do this, I get:

Traceback (most recent call last):
  File "/home/devinmatte/Documents/CSH/packet/app.py", line 1, in <module>
    from packet import app
  File "/home/devinmatte/Documents/CSH/packet/packet/__init__.py", line 34, in <module>
    "client_secret": app.config["INTRO_OIDC_CLIENT_SECRET"]
  File "/home/devinmatte/Documents/CSH/packet/.packet/lib/python3.6/site-packages/flask_pyoidc/flask_pyoidc.py", line 106, in __init__
    self.app.add_url_rule('/redirect_uri', 'redirect_uri', self._handle_authentication_response)
  File "/home/devinmatte/Documents/CSH/packet/.packet/lib/python3.6/site-packages/flask/app.py", line 66, in wrapper_func
    return f(self, *args, **kwargs)
  File "/home/devinmatte/Documents/CSH/packet/.packet/lib/python3.6/site-packages/flask/app.py", line 1221, in add_url_rule
    'existing endpoint function: %s' % endpoint)
AssertionError: View function mapping is overwriting an existing endpoint function: redirect_uri

Is there a way to create these two instances without getting a redirect_uri clash?
They also have the same value for the redirect_uri because it's the same service

Documentation improvement proposal

Hey there,

Thanks for the work on this lib :)

I wish to make a small PR on the documentation to add a mention to the nice examples you provid in the repo. I did not pay attention to the example and read only the doc and I miss few things which are not in the doc but in your examples (the init_app() method and the userinfo_endpoint var for ProviderMetadata instance). So I had to open the code and OpenID spec to find them, I would have earn time knowing the existence of the code example. And I think the userinfo_request is used in most of cases so I think it's nice to have in the documentation rather than having to open OpenID spec.
I wish to add those 2 details in the documentation and add a link to your examples as well, might help someone else.

Any thoughts ? I can go ahead ?

Given authenticate, but no token / access token receive

Hello,
i tryed to test your code, to get oidc client for our provider but it's doesn't give a token, authenticate is successfull

`def load_oidc():
from flask_pyoidc import OIDCAuthentication
from flask_pyoidc.provider_configuration import ClientMetadata, ProviderConfiguration
from app import app
am_base_url = app.config['AM_URL']
client = 'xxxxx'
secret = 'xxxxxx'
auth_params = {'scope': ['openid', 'profile']}

# static configuration

provider_metadata = ProviderMetadata(issuer=f'{am_base_url}/oauth2/IDP',
                                     authorization_endpoint=f'{am_base_url}/oauth2/IDP/authorize',
                                     jwks_uri=f'{am_base_url}/oauth2/IDP/connect/jwk_uri')

config_oidc = ProviderConfiguration(provider_metadata=provider_metadata, issuer=f'{am_base_url}/oauth2/IDP',
                               client_metadata=ClientMetadata(client_id=client, client_secret=secret),
                               auth_request_params=auth_params, userinfo_http_method='POST')

# dynamic config
# config_oidc = ProviderConfiguration(issuer=f'{am_base_url}/oauth2/IDP',
#                                     client_metadata=ClientMetadata(client_id=client, client_secret=secret),
#                                     auth_request_params=auth_params)

app.config.update({'OIDC_REDIRECT_URI': 'http://127.0.0.1:5000/oidc_callback',
                   'SECRET_KEY': 'xxxxxx',
                   'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=7).total_seconds(),
                   'DEBUG': True})


auth = OIDCAuthentication({'AM': config_oidc}, app)
return auth

`

with

`@app.route('/login_oidc')
@auth.oidc_auth('AM')
def login1():
user_session = UserSession(flask.session)

print(user_session)
print(user_session.id_token)
print(user_session.is_authenticated())
print(user_session.access_token)
print(user_session.userinfo)
return jsonify(access_token=user_session.access_token,
               id_token=user_session.id_token,
               userinfo=user_session.userinfo)

`
got

  • Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
    <flask_pyoidc.user_session.UserSession object at 0x050500B0>
    None
    True
    None
    None

we don't know why ?
do you miss something in configuration ?

Issue with https redirects

I'm still poking at this, but, basically, my oidc provider requires that there be an https redirect endpoint. However, when I try making a login request, the redirect portion of the url is http, even though I'm running the flask server is running in https mode. ie:

 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on https://localhost.localdomain:5000/ (Press CTRL+C to quit)

But the URL that gets passed to the oidc provider is

https://<provider>/isam/oidc/endpoint/amapp-runtime-oidcidp/authorize?client_id=<client_id>&response_type=code&scope=openid&redirect_uri=http%3A%2F%2Flocalhost.localdomain%3A5000%2Fredirect_uri&state=bgdozUX4bA4GK9pL&nonce=Tz97WJxkHyf9laQT

AttributeError: 'IdToken' object has no attribute 'jwt'

It appears that the code for f86dd44 is not working as intended or does not work with some IdPs. On a successful login, I get this stack trace:

Traceback (most recent call last):
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 2000, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1991, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1567, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1641, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1544, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1639, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask/app.py", line 1625, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/steven/.venv/conditional/lib/python3.5/site-packages/flask_pyoidc/flask_pyoidc.py", line 103, in _handle_authentication_response
    flask.session['id_token_jwt'] = id_token.jwt
AttributeError: 'IdToken' object has no attribute 'jwt'

What is PROVIDER_NAME used for?

I have successfully set up Flask-pyoidc with keycloak but I simply put a random string as provider name.

  • Is provider name just for info?
  • What is the "correct" approach? I'm new to authentication in general.

Based on the example in Flask-pyoidc\tests\test_flask_pyoidc.py my production code looks like this:

#blueprint setup
main_bp = Blueprint('main_bp', __name__,
                    template_folder='templates',
                    static_folder='static')

#auth setup
ISSUER = app.config['KEYCLOAK_DOMAIN']
CLIENT = app.config['KEYCLOAK_CLIENT']
PROVIDER_NAME = "pyoidc complaints if this is null. It would be embarrassing if anyone saw my hack."
PROVIDER_CONFIG = ProviderConfiguration(issuer=ISSUER,
                                         client_metadata=ClientMetadata(CLIENT, app.config['KEYCLOAK_SECRET']))

auth = OIDCAuthentication({PROVIDER_NAME: PROVIDER_CONFIG})
auth.init_app(app)

#routes
@main_bp.route('/profile', methods=['GET'])
@auth.oidc_auth(PROVIDER_NAME)
def profile():
    """Homepage route."""
    user_session = UserSession(flask.session)
    return jsonify(access_token=user_session.access_token,
                   id_token=user_session.id_token,
                   userinfo=user_session.userinfo)

Provide an auth flow without decorators to allow dynamic OIDC provider discovery

I'm using this lovely library to to authentication with a slightly non-standard OIDC provider. This provider requires a non-standard discovery URL, which takes a dynamic URL param. It returns different configurations depending on the parameter.

I cannot use the default @auth.oidc_auth('default') decorator because I need to build the OIDCAuthentication on the fly for each request. I end up doing something like this

@application.route('/auth/cb')
def auth_callback():
  provider_metadata = discover_oidc_provider_metadata(request.args) # do custom provider discovery
  client_metadata = ClientMetadata(CLIENT_ID, CLIENT_SECRET)
  config = ProviderConfiguration(client_metadata=client_metadata, provider_metadata=provider_metadata)
  client = PyoidcFacade(config, redirect_uri)
  openid = OIDCAuthentication({})
  return openid._authenticate(client) # using protected function :(

I'd love to be able to use a non-protected function to start the authentication flow after doing the provider discovery manually.

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.