better separation between api and frontend login

This commit is contained in:
2019-04-02 10:47:53 +02:00
parent ed57dc2720
commit 024f063bea
7 changed files with 80 additions and 14 deletions

View File

@@ -5,12 +5,17 @@ Backend base module
from flask import Flask from flask import Flask
from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth from flask_httpauth import HTTPTokenAuth, HTTPBasicAuth, MultiAuth
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) app = Flask(__name__)
app.config.from_object('backend.config.Config') app.config.from_object('backend.config.Config')
db = SQLAlchemy(app) db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
jwt_auth = HTTPTokenAuth() jwt_auth = HTTPTokenAuth()
basic_auth = HTTPBasicAuth() basic_auth = HTTPBasicAuth()
multi_auth = MultiAuth(basic_auth, jwt_auth) multi_auth = MultiAuth(basic_auth, jwt_auth)

View File

@@ -2,7 +2,10 @@
""" """
This module provides functions related to authentication through the API. This module provides functions related to authentication through the API.
For example: listing of available auth providers or registration of users. For example: listing of available auth providers or registration of users.
Login through API does not start a new session, but instead returns JWT.
""" """
import flask
from datetime import datetime, timedelta from datetime import datetime, timedelta
import jwt import jwt
from flask import request, jsonify, current_app, url_for from flask import request, jsonify, current_app, url_for
@@ -12,9 +15,9 @@ from random import randint
from flask_login import logout_user, login_user from flask_login import logout_user, login_user
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
from backend import db from backend import db, app
from backend.api import auth_api_bp from backend.api import auth_api_bp
from backend.auth import AUTH_PROVIDERS from backend.auth import AUTH_PROVIDERS, oidc_auth
from backend.models.user_model import User from backend.models.user_model import User
@@ -62,11 +65,39 @@ def login():
return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401 return jsonify({'message': 'Invalid credentials', 'authenticated': False}), 401
token = create_jwt(user) token = create_jwt(user)
#login_user(user)
return jsonify({'token': token.decode('UTF-8')}) return jsonify({'token': token.decode('UTF-8')})
@auth_api_bp.route('/logout', methods=('GET', ))
def logout(): def create_or_retrieve_user_from_userinfo(userinfo):
return jsonify({'message': 'Not yet implemented!', 'authenticated': False}), 401 try:
#logout_user() email = userinfo["email"]
except KeyError:
return None
user = User.get_by_identifier(email)
if user is not None:
app.logger.info("user found")
return user
user = User(email=email, first_name=userinfo.get("given_name", ""),
last_name=userinfo.get("family_name", ""))
app.logger.info("creating new user")
db.session.add(user)
db.session.commit()
return user
@auth_api_bp.route('/oidc', methods=['GET'])
@oidc_auth.oidc_auth()
def oidc():
user = create_or_retrieve_user_from_userinfo(flask.session['userinfo'])
if user is None:
return "Could not authenticate: could not find or create user.", 401
if current_app.config.get("AUTH_RETURN_EXTERNAL_JWT", False):
token = jwt.encode(flask.session['id_token'], current_app.config['SECRET_KEY'])
else:
token = create_jwt(user)
return token

View File

@@ -5,13 +5,16 @@ Base module for auth aspects.
Also this module contains mainly code for login through HTML pages served by the backend. Also this module contains mainly code for login through HTML pages served by the backend.
If frontend pages are build by frontend code (JS, etc.) authentication should consider using api functions. If frontend pages are build by frontend code (JS, etc.) authentication should consider using api functions.
(For more info, see api.auth_api.py.) (For more info, see api.auth_api.py.)
This code uses login_user and logout user (to start and end sessions) ... API code returns JWTs.
""" """
from flask import Blueprint from flask import Blueprint, jsonify
from flask_login import logout_user, LoginManager
from werkzeug.routing import BuildError from werkzeug.routing import BuildError
auth_bp = Blueprint('auth', __name__, url_prefix='/auth', template_folder='templates') auth_bp = Blueprint('auth', __name__, url_prefix='/auth', template_folder='templates')
from backend.auth.config import AUTH_PROVIDERS, DEFAULT_PROVIDER from backend.auth.config import AUTH_PROVIDERS, DEFAULT_FRONTEND_PROVIDER
from backend.auth.oidc_config import OIDC_PROVIDERS from backend.auth.oidc_config import OIDC_PROVIDERS
from backend.auth.oidc import oidc_auth from backend.auth.oidc import oidc_auth
@@ -26,7 +29,7 @@ def auth_decorator(): # custom decorator
@auth_bp.route('/login', methods=['GET', 'POST']) @auth_bp.route('/login', methods=['GET', 'POST'])
def login(): def login():
try: try:
prov = AUTH_PROVIDERS[DEFAULT_PROVIDER] prov = AUTH_PROVIDERS[DEFAULT_FRONTEND_PROVIDER]
except KeyError: except KeyError:
return "No known default provider specified!" return "No known default provider specified!"
url = prov["url"] url = prov["url"]
@@ -41,3 +44,8 @@ def login():
@auth_bp.route('/login_select', methods=['GET']) @auth_bp.route('/login_select', methods=['GET'])
def login_select(): def login_select():
return render_template('login_select.html', providers=AUTH_PROVIDERS) return render_template('login_select.html', providers=AUTH_PROVIDERS)
@auth_bp.route('/logout', methods=('GET', ))
def logout():
logout_user()

View File

@@ -1,5 +1,7 @@
# Route for handling the login page logic # Route for handling the login page logic
from flask import request, redirect, render_template, url_for from flask import request, redirect, render_template, url_for
from flask_login import login_user
from backend.auth import auth_bp from backend.auth import auth_bp
@@ -10,5 +12,9 @@ def base_login():
if request.form['username'] != 'admin' or request.form['password'] != 'admin': if request.form['username'] != 'admin' or request.form['password'] != 'admin':
error = 'Invalid Credentials. Please try again.' error = 'Invalid Credentials. Please try again.'
else: else:
login_user()
return redirect("/") return redirect("/")
return render_template('login.html', error=error) return render_template('login.html', error=error)

View File

@@ -11,7 +11,19 @@ AUTH_PROVIDERS: Dict[str, Dict[str, str]] = {
"type": "login_form", "type": "login_form",
"url": "auth.base_login" "url": "auth.base_login"
}, },
"KIT OIDC (API)":
{
"type": "api_oidc",
"url": "auth_api_bp.oidc"
},
"User-Password (API)":
{
"type": "api_login_form",
"url": "auth_api_bp.base_login"
},
} }
DEFAULT_PROVIDER: str = "Base Login" #DEFAULT_PROVIDER: str = "Base Login"
#DEFAULT_PROVIDER: str = "KIT OIDC" DEFAULT_PROVIDER: str = "KIT OIDC (API)"
DEFAULT_FRONTEND_PROVIDER: str = "Base Login"

View File

@@ -5,6 +5,7 @@ OIDC login auth module
import flask import flask
from flask import jsonify from flask import jsonify
from flask_login import login_user
from flask_pyoidc.flask_pyoidc import OIDCAuthentication from flask_pyoidc.flask_pyoidc import OIDCAuthentication
from flask_pyoidc.user_session import UserSession from flask_pyoidc.user_session import UserSession
@@ -50,8 +51,9 @@ def create_or_retrieve_user_from_userinfo(userinfo):
@oidc_auth.oidc_auth() @oidc_auth.oidc_auth()
def oidc(): def oidc():
user_session = UserSession(flask.session) user_session = UserSession(flask.session)
create_or_retrieve_user_from_userinfo(user_session.userinfo) app.logger.info(user_session.userinfo)
#login_user(user) user = create_or_retrieve_user_from_userinfo(user_session.userinfo)
login_user(user)
return jsonify(id_token=user_session.id_token, return jsonify(id_token=user_session.id_token,
access_token=flask.session['access_token'], access_token=flask.session['access_token'],
userinfo=user_session.userinfo) userinfo=user_session.userinfo)

View File

@@ -68,6 +68,8 @@ class Config():
JWT_ALGORITHM = "HS256" JWT_ALGORITHM = "HS256"
JWT_EXP_DELTA_SECONDS = 5 * 60 JWT_EXP_DELTA_SECONDS = 5 * 60
AUTH_RETURN_EXTERNAL_JWT = False
INDEX_TEMPLATE = "index.html" INDEX_TEMPLATE = "index.html"