The aim of this document is to provide a simple guide on how to create a web application that requires user to login via OpenID connect to view a specific page. The web application is built with Python 3 and the Flask. The user login session is managed with the help of Flask-Login and Flask-Session. See setup.py for the dependencies.
Let's make sure that everything is running as promised.
python3 -mvenv env
pip install --editable .
2. Setup the following environment variables. GOOGLE_CLIENT_ID
and GOOGLE_CLIENT_SECRET
can be obtained from Google's Credentials page, see section "Obtain OAuth 2.0 credentials" in this guide.
export FLASK_ENV=development
export GOOGLE_CLIENT_ID=
export GOOGLE_CLIENT_SECRET=
export FLASK_APP=src/simple_login/app.py
flask run
app
4. Visit localhost:5000 to confirm the app is running.
Process with the "login from here" and complete the login. Then visit localhost:5000/secret We should be able to see
Only logged in user can see this.
As the entry_points
in setup.py indicates, the application
starts from ./src/simple_login/app.py.
The configuations of the application are stored in the environment. This is one of the practices recommended by the Twelve-Factor App.
GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", default="")
To load the configurations into our application, we use,
app.config.from_object(__name__)
which loads only the uppercase attributes of the module/class. To access the configurations within the application, we do
current_app.config["GOOGLE_CLIENT_ID"]
Check out the official documentation to learn more about configuration handling.
The default Flask's session stores the values in cookies not on the server side by default. This means that we should not store anything sensitive in the default cookie. For server side session, we can use the extension Flask-Session Here in our example we setup the application to store the session in our local filesystem.
SESSION_TYPE = 'filesystem'
Session(app)
Then use the flask.session object which works like an ordinary dict to access the values:
session['state'] = state # Store
v = session['state'] # Retrieve
We can manage user login with the Flask-Login library which store active user's ID in the session that we've setup above.
login_manager = LoginManager()
login_manager.init_app(app)
In addition to the setup above, we also need to provide a our user class.
class User(object):
and a user_loader
callback which will return either the user of a given user_id
from the database,
or None if the Id is invalid:
@login_manager.user_loader
def load_user(user_id) -> Optional[User]:
app.logger.debug('looking for user %s', user_id)
u = users.get(user_id, None)
app.logger.debug('id is %s', id)
if not id:
return None
return u
For the sake of simplicity, our database is just a simple dict within the application.
users: Dict[str, User] = {}
-
When a user visits localhost:5000, Flask will render the index page using the templates/index.html that lead the user to localhost:5000/login.
-
The login process starts with creating an anti-forgery state token, and nonce for replay protection. Both values are stored in the server-side session and the client (browser) holds the session ID in cookies.
-
Then an authentication request is sent to Google and the user will be redirect to the Google consent page.
-
The response after the consent is given will be received by the
/callback
endpoint. At this point we should verify that the value of thestate
from Google matches the one we've stored in the session. -
Using the
code
parameter from the response, a POST request is made to Google for exchanging the access token and ID token. -
The
id_token
(a JWT) field should be found in the successful response to the POST request. After base64 decode theid_token
we should verify thenonce
field and remove thenonce
from our session. Then we create the our application User based on the id obtained from thesub
field, store it in ourusers
DB, and login the user with Flask-Login:login_user(u)
.