This sample shows how to invoke OpenID Connect/OAuth 2 protocol to:
- authenticate an existing user
- change user password by user itself
- show user information
using PingOne for Customers (Ping14C) Authentication and Management API services.
The default OAuth 2.0 flow illustrated here is an authorization code grant type. But for a demonstration purposes you can test implicit
and client credentials
types with corresponding config.cfg file adjusting.
You will need the following things:
- PingOne for Customers Account - If you don’t have an existing one, please register it.
- An OpenID Connect Application. Instructions for creating one can be found here.
Also make sure that it is enabled and access grants(
phone profile address email openid p1:read:userPassword p1:reset:userPassword
) by scopes are properly set. - At least one user in the same environment as the application (not assigned)
- To have installed Python 3.8 or lower
- This sample assumes the redirect uri registered with the client application is something else than the host name (i.e
http://localhost:8080
) to properly catch code in URL. If you have something likehttp://localhost:8080/custom_callback
then setOAUTH_REDIRECT_PATH = '/custom_callback'
in config.cfg and update@app.route
in app.py as:@app.route('/custom_callback', methods=['GET'], endpoint='callback') @auth.callback @csrf.exempt def callback(): return redirect(url_for('index'))
-
Copy this source code:
git clone git@github.com:pingidentity/pingone-sample-python.git
-
If you have already different python projects, try to keep their dependencies separate by creating isolated python virtual environments for them. Otherwise, you can skip this step. So, if you don't use an IDE that is able to configure a virtual environment (virtualenv), then you can create one with:
python3 -m venv _venv
where_venv
is a path to a new virtual environment Once a virtual environment has been created, it then should be “activated” via:source _venv/bin/activate
-
Install all requirements by:
pip3 install -r requirements.txt
-
Grab the following application configuration information from the admin console to replace placeholders in config.cfg with it: ENVIRONMENT_ID, CLIENT_ID, CLIENT_SECRET (if necessary), OAUTH_REDIRECT_PATH
-
Start an application by:
python3 app.py
- Flask Message Flashing
- Requests-OAuthlib
- PyJWT with cryptography for tokens decoding
- Since Oauth2 works through SSL layer, if your server is not parametrized to allow HTTPS, the
fetch_token
method will raise an oauthlib.oauth2.rfc6749.errors.InsecureTransportError. So, while testing, you can disable Oauth2 check by uncommenting theos.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
in app.py. For more information check this article. - For the simplicity. on-the-fly certificates are used, which are useful to quickly serve an application over HTTPS without having to mess with certificates:
ssl_context='adhoc'
. But note that each time the server runs, a different certificate is generated on the fly through pyOpenSSL. So you can generate self-signed certificate(i.e with openssl) and by setting thessl_context
argument in app.run() to a tuple with the filenames of the certificate and private key files, that will be the same every time you launch your server for production please use real certificates
CSRF protection is enabled globally in this sample. It requires a secret key to securely sign the token. By default this will use the Flask app's SECRET_KEY
. If you'd like to use a separate token you can set WTF_CSRF_SECRET_KEY
.
To check coding conventions for the Python code see Style Guide for Python Code
-
SECRET_KEY
is critical in applications config.cfg: this variable needs to exist in our config for sessions to function properly. -
Unlike cookie-based sessions, Flask sessions are handled and stored on the server-side. A session object is simply a dict which is accessible throughout the application a global level, referring to a single 'logged-in user'.
-
Flask will suppress any server error with a generic error page unless it is in debug mode. As such to enable just the interactive debugger without the code reloading, you have to invoke run() with
debug=True
anduse_reloader=False
. Settinguse_debugger
to True without being in debug mode won’t catch any exceptions because there won’t be any to catch. -
If you have seen an error like
“OSError: [Errno 8] Exec format error”
when runningpython3 app.py
in debug mode (that is by default), then this may be because Flask is trying to runapp.py
directly on your system rather than with the python binarypython3 app.py
, that is not working sinceapp.py
isn't executable.
To workaroud this:- add the following line (shebang) at the top of app.py:
#!/usr/bin/env python3
- make the file executable:
chmod +x flaskblog.py
. - run
python3 app.py
.
- add the following line (shebang) at the top of app.py:
-
Comment
ENV='development'
in config.cfg to disable Debug Mode.