Connecting an Ember App To An OAuth2 Python Flask Server

This article is going to go over some of the basics of connecting your Ember Octane based app to an OAuth2 Flask server (which we created in the last article ). This is a fairly straight forward and quick process with the help of a few plugins that make the whole flow much simpler.

The main piece of the puzzle is Ember Simple Auth. It’s a lightweight session management tool which will take care of automatically managing and renewing your tokens in the background and eliminates a massive amount of boilerplate code on new projects.

To get started either create a new ember app or edit an existing one.

First step is to install ember simple auth with ember-cli

ember install ember-simple-auth

This will add a session service to your project that you can query for the current state of the user. This will let create a simple login component for the user to enter their username and password

<div class="login-form container">
<!-- hide everything if we're already authenticated, you could put a welcome message instead-->
  {{#unless this.session.isAuthenticated}}
    {{#if this.errorMessage}}
      <div class="error">Error: {{this.errorMessage}}</div>
    {{/if}}
    <form class="col s12">
        <div class="row">
          <div class="input-field col s4 offset-s4">
            <Input id="email" type="email" class="validate" @value={{this.username}}/>
            <label for="email">E-mail</label>
          </div>
        </div>
        <div class="row">
          <div class="input-field col s4 offset-s4">
            <Input id="password" type="text" class="validate" @value={{this.password}}/>
            <label for="password">Password</label>
          </div>
        </div>
      </form>
      <button type="button" class="login-btn btn" {{on "click" this.login}} role="button">
         <span>Login</span></button>
    </div>
  {{/unless}}
</div>
{{yield}}
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';

export default class LoginComponent extends Component {
  @service session;
  @service router;
  @tracked errorMessage = '';
  @tracked username;
  @tracked password;

  @action
  async login() {
    try {
      await this.session.authenticate('authenticator:custom-oauth2', 'password', this.username, this.password);
    } catch(error) {
      this.errorMessage = error.error || error;
    }
   //if we are authenticated navigate back to the homepage
    if (this.session.isAuthenticated) {
      this.router.transitionTo('index');
    }
  }
  @action
  async invalidateSession() {
    this.session.invalidate();
  }
}

The next piece that needs added for ember-simple-auth is a custom authenticator, this is the piece that tells ember-simple-auth how to connect and authenticate with your authentication server. These usually don’t contain more than just your server details. Using our example from the last article, that would look something like this :

ember g authenticator custom-oauth2
import ENV from "my-app/config/environment";
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
import { inject as service } from '@ember/service';
const PASSWORD_GRANT = "password";

export default class CustomAuthenticator extends OAuth2PasswordGrant {
  @service session;
  serverTokenEndpoint = ENV.TOKEN_ENDPOINT;
  clientId = ENV.CLIENT_ID;
  serverTokenRevocationEndpoint = ENV.REVOKE_TOKEN_ENDPOINT;

  async authenticate(provider, username, password, scope = [], headers = {}) {
    if(provider=== PASSWORD_GRANT) {
      return super.authenticate(username, password, scope, headers);
    } 
}

Now we could make this simpler and just use the default authenticate method, but there’s a few advantages to defining a custom authenticate function that we’ll go over in detail in the next article.

If you are having issues with the server accepting your username and password, the best place to debug is to set a breakpoint in your server code here :

@oauth_bp.route('/oauth/authorize', methods=['GET', 'POST'])
def authorize():
    user = current_user()
    if request.method == 'GET':
        try:
            grant = authorization.validate_consent_request(end_user=user)
        except OAuth2Error as error:
            return error.error
        return render_template('authorize.html', user=user, grant=grant)
    if not user and 'username' in request.form:
        email = request.form.get('username')
        user = User.query.filter_by(email=email).first()
    
    return authorization.create_authorization_response(grant_user=user)

You should be able to step through the authlib code and find out where it is failing, in my experiance it’s usually in one of two places, either the client configuration is missing the needed grant type, or the user’s password is not being validated correctly. Double check the config settings stored for your client id in your database, and make sure they match your settings in the authenticator! Also check what’s being posted to the server from the browsers network tab, and compare it to your client settings in the backend database.

The ENV variables are just the settings for talking to your authenitcation server, and in my case look something like the following. I personally like to use ember-cli-dotenv to store my variables which makes it easy to have different setting for each test environment

CLIENT_ID=CUSTOM_CLIENT_ID_CREATED_IN_FLASK
TOKEN_ENDPOINT=https://localhost:5000/oauth/token
REVOKE_TOKEN_ENDPOINT=https://localhost:5000/oauth/revoke

So the final step is how do you us this to secure your pages? There’s a ember-simple-auth mixin that does all the heavy lifting, that you can use like the following in your route classes.

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default class ListUsersRoute extends Route.extend(AuthenticatedRouteMixin, {}) {
  @service store;
  model() {
    return this.store.findAll('user')
  }
}

This will only allow authenticated users to access this route, all others will be redirected to the default page assigned in ember-simple-auth which is the /login route.

Using ember-simple-auth to access your Rest server

This is also trivial with ember-simple-auth

generate you application adapter if you don’t already have one

ember g adapter application

and add the following:

import JSONAPIAdapter from '@ember-data/adapter/json-api';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
import { computed } from '@ember/object';

export default class ApplicationAdapter extends JSONAPIAdapter.extend(DataAdapterMixin) {
  host = ENV.API_HOST + ':' + ENV.API_PORT;
  namespace = 'api';
  @computed('session.data.authenticated.access_token')
  get headers() {
    let headers = {};
    if (this.session.isAuthenticated) {
      // OAuth 2
      headers['Authorization'] = `Bearer ${this.session.data.authenticated.access_token}`;
    }

    return headers;
  }
}

and that it, now every ember data request will automatically add your session token to the request headers, flask will parse out that token and validate your access before processing the request.

Hopefully this was helpfull, Ember-simple-auth is usually one of the first add-ons I install in a new Ember project as it really makes these interactions easy without a whole lot of code to write. In the next article we’ll take this code and extend it to allow you to login with Facebook or Google.

Using OAuth2 with Flask-REST-JSONAPI

This is hopefully the first in a series of posts about adding oauth2 support to a basic web project using Flask and Ember. In the next few weeks there should be follow up articles on adding oauth2 support to Ember to talk to our backend server, as well as how to add Google and Facebook authentiction.

Authentication is confusing at the best of times, there’s so many terms and definitions that you tend to learn and use once while configuring authentication then you never think of them again. This post will help go over some of the basics of setting up a base Authentication program for your application and use a few common libraries to do so.

Authlib is a flexible OAuth library for Python. It is a very flexible framework that can be adapted to work in a variety of situations. I’ve been using Flask-REST-JSONAPI lately as I tend to use EmberJS for my frontend work and it’s support for the JSONAPI standard works seamlessly with Ember-data.

OAuth2 enables a third-party application to obtain limited access to a http service. The basic workflow for OAuth2 is as follows: 

  • The Client sends login identifiers to an authorization server
  • The authorization Server verifies the clients identity and returns a token to the client
  • The client sends the token to the resources server when requesting a resource
  • The resource server verifies the validity of the token, and if valid returns the resource to the client

Authorization servers may support several grant types. A grant type defines a way of how the authorization server will verify the request and issue the token.

Common Grant types are:

  • Authorization Code Grant – The client has a code that they can exchange for a token
  • Password Grant – The client has a username / password they can exchange for a token
  • Refresh Token Grant – The Client has a token that can be exchanged for an auth token

Authorization servers can require that Clients verify themselves before they can request a token on behalf of a user. A Client must provide it’s client information to obtain an access token.

Methods to do this are the following:

  • None – public client and no client secret
  • Client Secret – a code given to a client used to verify its self

For this example, I relied heavily on this documentation https://docs.authlib.org/en/latest/flask/2/index.html#flask-oauth2-server

First of all we need to create the models needed for our Authentication server, Authlib provides several base classes that simplifies the work needed to create tables and models for your server

import time
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

from authlib.integrations.sqla_oauth2 import (
   OAuth2ClientMixin,
   OAuth2AuthorizationCodeMixin,
   OAuth2TokenMixin,
)

class OAuth2Client(db.Model, OAuth2ClientMixin):
   __tablename__ = 'oauth2_client'

   id = db.Column(db.Integer, primary_key=True)
   user_id = db.Column(
       db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
   user = db.relationship('User')

This represents a client in our system. This will create a table to store the client secret issued to your clients, as well as metadata specific to the client.

The metadata is stored as a JSON Field that stores the following information

  • Client_name
  • Client URI
  • Grant types
  • Redirect uris
  • Response types
  • Scope
  • token_endpoint_auth_method
class OAuth2Client(db.Model, OAuth2ClientMixin):
   __tablename__ = 'oauth2_client'

   id = db.Column(db.Integer, primary_key=True)
   user_id = db.Column(
       db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
   user = db.relationship('User')

This will store the tokens issued to the users in your system. It stores the client they authenticated with, the token itself, scopes, issued and expiry dates, and a reference to the user it’s associated with.

class OAuth2Token(db.Model, OAuth2TokenMixin):
   __tablename__ = 'oauth2_token'

   id = db.Column(db.Integer, primary_key=True)
   user_id = db.Column(
       db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
   user = db.relationship('User')

   def is_refresh_token_active(self):
       if self.revoked:
           return False
       expires_at = self.issued_at + self.expires_in * 2
       return expires_at >= time.time()

Our user class we will keep really simple, but for this authentication we are going to use email as the username instead of a custom username field. You will also notice we’re using passlib to verify a sha256 password hash on the user’s password

from passlib.hash import sha256_crypt

class User(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   first_name = db.Column(db.String)
   last_name = db.Column(db.String)
   email = db.Column(db.String)
   password = db.Column(db.String)

   def get_user_id(self):
       return self.id

   def check_password(self, password):
      return sha256_crypt.verify(self.password, password)

Next we need to add support for how the authentication server will validate a simple password grant from a client.

from authlib.oauth2.rfc6749 import grants

class PasswordGrant(grants.ResourceOwnerPasswordCredentialsGrant):
   TOKEN_ENDPOINT_AUTH_METHODS = [
       'none', 'client_secret_basic', 'client_secret_post'
   ]
   def authenticate_user(self, email, password):
       user = User.query.filter_by(email=email).first()
       if user is not None and user.check_password(password):
           return user
from authlib.integrations.flask_oauth2 import AuthorizationServer, ResourceProtector

from authlib.oauth2 import OAuth2Error


query_client = create_query_client_func(db.session, OAuth2Client)
save_token = create_save_token_func(db.session, OAuth2Token)
authorization = AuthorizationServer(
   query_client=query_client,
   save_token=save_token,
)

def config_oauth(app):

   authorization.init_app(app)
   authorization.register_grant(PasswordGrant)
   
   # support revocation
   revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
   authorization.register_endpoint(revocation_cls)
  bearer_cls = create_bearer_token_validator(db.session, OAuth2Token)
  



@oauth_bp.route('/oauth/authorize', methods=['GET', 'POST'])
def authorize():
   user = current_user()
   if request.method == 'GET':
       try:
           grant = authorization.validate_consent_request(end_user=user)
       except OAuth2Error as error:
           return error.error
       return render_template('authorize.html', user=user, grant=grant)
   if not user and 'username' in request.form:
       email = request.form.get('username')
       user = User.query.filter_by(email=email).first()
   return authorization.create_authorization_response(grant_user=user)

@oauth_bp.route('/oauth/token', methods=['POST'])
def issue_token():
   return authorization.create_token_response(request)


@oauth_bp.route('/oauth/revoke', methods=['POST'])
def revoke_token():
   return authorization.create_endpoint_response('revocation')

@oauth_bp.route('/api/me')
@require_oauth(‘ALL’)
def api_me(**kwargs):
   user = kwargs['current_user']
   return jsonify(id=user.id, email=user.email)

The TOKEN_ENDPOINT_AUTH_METHODS define how it will verify your client before doing the username / password check.

The authenticate_user function is called when the Authentication server has a username password combination to look up. These are passed by the client in form post data to be verified.

Here we do a simple query in our database to find the user that corresponds to an email, and verifies their password is correct. We then return the user object back to the auth server request.

Next we need to initialize the auth server and configure it’s routes:

from authlib.integrations.flask_oauth2 import AuthorizationServer, ResourceProtector

from authlib.oauth2 import OAuth2Error


query_client = create_query_client_func(db.session, OAuth2Client)
save_token = create_save_token_func(db.session, OAuth2Token)
authorization = AuthorizationServer(
   query_client=query_client,
   save_token=save_token,
)

def config_oauth(app):

   authorization.init_app(app)
   authorization.register_grant(PasswordGrant)
   
   # support revocation
   revocation_cls = create_revocation_endpoint(db.session, OAuth2Token)
   authorization.register_endpoint(revocation_cls)
  bearer_cls = create_bearer_token_validator(db.session, OAuth2Token)
  



@oauth_bp.route('/oauth/authorize', methods=['GET', 'POST'])
def authorize():
   user = current_user()
   if request.method == 'GET':
       try:
           grant = authorization.validate_consent_request(end_user=user)
       except OAuth2Error as error:
           return error.error
       return render_template('authorize.html', user=user, grant=grant)
   if not user and 'username' in request.form:
       email = request.form.get('username')
       user = User.query.filter_by(email=email).first()
   return authorization.create_authorization_response(grant_user=user)

@oauth_bp.route('/oauth/token', methods=['POST'])
def issue_token():
   return authorization.create_token_response(request)


@oauth_bp.route('/oauth/revoke', methods=['POST'])
def revoke_token():
   return authorization.create_endpoint_response('revocation')

@oauth_bp.route('/api/me')
@login_required
def api_me(**kwargs):
  """ And finally here is a route to test that auth is working"""
   user = kwargs['current_user']
   return jsonify(id=user.id, email=user.email)

So now if you make a request to /oauth/authorize with the user login details in a form post

Then the endpoint will return you a valid token. You can then set this token in your header to request resources on your server (using the @login_required )

The login_required is a simple wrapper that does the following to verify your token, it simply looks for the provided token in the database and if valid allows the endpoint to execute:

import json
from functools import wraps

from flask import request, make_response

from flask_rest_jsonapi.errors import jsonapi_errors
from flask_rest_jsonapi.utils import JSONEncoder


def login_required(func):
   """Check that the user is logged in and has access
   :param callable func: the function to decorate
   :return callable: the wrapped function
   """
   @wraps(func)
   def wrapper(*args, **kwargs):
       if not 'Authorization' in request.headers:
           error = json.dumps(jsonapi_errors([{'source': '',
                                               'detail': 'A user must be logged in to view this resource',
                                               'title': 'No Authorization Header',
                                               'status': '403'}]), cls=JSONEncoder)
           return make_response(error, 403, {'Content-Type': 'application/vnd.api+json'})
       token_string = request.headers['Authorization'][7:]
       token = OAuth2Token.query.filter_by(access_token=token_string).first()
       if not token or token.revoked:
           error = json.dumps(jsonapi_errors([{'source': '',
                                               'detail': 'A user must be logged in to view this resource',
                                               'title': 'Invalid Authorization Token',
                                               'status': '403'}]), cls=JSONEncoder)
           return make_response(error, 403, {'Content-Type': 'application/vnd.api+json'})

       return func(*args, **dict(kwargs, current_user=token.user))
   return wrapper

Then in your request header you would add:

Authorization : Bearer TOKEN_GOES_HERE

And when you make the request to /api/me , It will return you your current users id and email.