Implemented oidc with hydra

This commit is contained in:
Luka 2022-01-18 09:48:18 +00:00
parent 26ffb28a41
commit 2160f634d1
9 changed files with 87 additions and 16 deletions

3
app.py
View file

@ -13,10 +13,12 @@ from areas import auth
from helpers import ( from helpers import (
BadRequest, BadRequest,
KratosError, KratosError,
HydraError,
bad_request_error, bad_request_error,
validation_error, validation_error,
kratos_error, kratos_error,
global_error, global_error,
hydra_error,
) )
from config import * from config import *
@ -30,6 +32,7 @@ app.register_error_handler(Exception, global_error)
app.register_error_handler(BadRequest, bad_request_error) app.register_error_handler(BadRequest, bad_request_error)
app.register_error_handler(ValidationError, validation_error) app.register_error_handler(ValidationError, validation_error)
app.register_error_handler(KratosError, kratos_error) app.register_error_handler(KratosError, kratos_error)
app.register_error_handler(HydraError, hydra_error)
jwt = JWTManager(app) jwt = JWTManager(app)

View file

@ -1,21 +1,26 @@
from flask import request, jsonify from flask import jsonify
from flask_jwt_extended import create_access_token from flask_jwt_extended import create_access_token
from flask_cors import cross_origin from flask_cors import cross_origin
from datetime import timedelta
from areas import api_v1 from areas import api_v1
from config import *
USERNAME = 'admin' from helpers import HydraOauth
PASSWORD = 'admin'
@api_v1.route('/login', methods=['POST']) @api_v1.route("/login", methods=["POST"])
@cross_origin() @cross_origin()
def login(): def login():
username = request.json.get('username') authorization_url = HydraOauth.authorize()
password = request.json.get('password') return jsonify({"authorizationUrl": authorization_url})
if username != USERNAME or password != PASSWORD:
return jsonify({'errorMessage': 'Invalid username or password'}), 401
access_token = create_access_token(identity=username) @api_v1.route("/hydra/callback")
return jsonify({'username': USERNAME, 'access_token': access_token}) @cross_origin()
def hydra_callback():
token = HydraOauth.get_token()
access_token = create_access_token(
identity=token, expires_delta=timedelta(days=365)
)
return jsonify({"access_token": access_token})

View file

@ -1,4 +1,8 @@
import os import os
SECRET_KEY = os.environ.get('SECRET_KEY') SECRET_KEY = os.environ.get("SECRET_KEY")
KRATOS_URL = os.environ.get('KRATOS_URL') KRATOS_URL = os.environ.get("KRATOS_URL")
HYDRA_CLIENT_ID = os.environ.get("HYDRA_CLIENT_ID")
HYDRA_CLIENT_SECRET = os.environ.get("HYDRA_CLIENT_SECRET")
HYDRA_AUTHORIZATION_BASE_URL = os.environ.get("HYDRA_AUTHORIZATION_BASE_URL")
TOKEN_URL = os.environ.get("TOKEN_URL")

View file

@ -1,2 +1,3 @@
from .kratos_api import * from .kratos_api import *
from .error_handler import * from .error_handler import *
from .hydra_oauth import *

View file

@ -6,6 +6,10 @@ class KratosError(Exception):
pass pass
class HydraError(Exception):
pass
class BadRequest(Exception): class BadRequest(Exception):
pass pass
@ -24,11 +28,17 @@ def validation_error(e):
def kratos_error(e): def kratos_error(e):
message = e.args[0] if e.args else "Failed to contact Kratos." message = "[KratosError] " + e.args[0] if e.args else "Failed to contact Kratos."
status_code = e.args[1] if e.args else 500
return jsonify({"errorMessage": message}), status_code
def hydra_error(e):
message = "[HydraError] " + e.args[0] if e.args else "Failed to contact Hydra."
status_code = e.args[1] if e.args else 500 status_code = e.args[1] if e.args else 500
return jsonify({"errorMessage": message}), status_code return jsonify({"errorMessage": message}), status_code
def global_error(e): def global_error(e):
message = str(e) message = str(e)
return jsonify({"errorMessage": message}) return jsonify({"errorMessage": message}), 500

41
helpers/hydra_oauth.py Normal file
View file

@ -0,0 +1,41 @@
from flask import request, session
from requests_oauthlib import OAuth2Session
from config import *
from helpers import HydraError
class HydraOauth:
SESSION_KEY = "oauth_state"
@staticmethod
def authorize():
try:
hydra = OAuth2Session(HYDRA_CLIENT_ID)
authorization_url, state = hydra.authorization_url(
HYDRA_AUTHORIZATION_BASE_URL
)
# State is used to prevent CSRF, keep this for later.
session[HydraOauth.SESSION_KEY] = state
return authorization_url
except Exception as err:
raise HydraError(str(err), 500)
@staticmethod
def get_token():
try:
hydra = OAuth2Session(
HYDRA_CLIENT_ID, state=session[HydraOauth.SESSION_KEY]
)
token = hydra.fetch_token(
TOKEN_URL,
client_secret=HYDRA_CLIENT_SECRET,
authorization_response=request.url,
)
session["hydra_token"] = token
return token
except Exception as err:
raise HydraError(str(err), 500)

View file

@ -18,6 +18,8 @@ class KratosApi:
res = requests.get("{}{}".format(KRATOS_URL, url)) res = requests.get("{}{}".format(KRATOS_URL, url))
KratosApi.__handleError(res) KratosApi.__handleError(res)
return res return res
except KratosError as err:
raise err
except: except:
raise KratosError() raise KratosError()

View file

@ -17,6 +17,7 @@ jsonschema==4.3.2
Jinja2==3.0.3 Jinja2==3.0.3
MarkupSafe==2.0.1 MarkupSafe==2.0.1
mypy-extensions==0.4.3 mypy-extensions==0.4.3
oauthlib==3.1.1
pathspec==0.9.0 pathspec==0.9.0
platformdirs==2.4.0 platformdirs==2.4.0
pycparser==2.21 pycparser==2.21
@ -24,6 +25,7 @@ PyJWT==2.3.0
pyrsistent==0.18.0 pyrsistent==0.18.0
regex==2021.11.10 regex==2021.11.10
requests==2.26.0 requests==2.26.0
requests-oauthlib==1.3.0
six==1.16.0 six==1.16.0
tomli==1.2.3 tomli==1.2.3
typing-extensions==4.0.1 typing-extensions==4.0.1

View file

@ -22,5 +22,8 @@ export FLASK_APP=app.py
export FLASK_ENV=development export FLASK_ENV=development
export SECRET_KEY="e38hq!@0n64g@qe6)5csk41t=ljo2vllog(%k7njnm4b@kh42c" export SECRET_KEY="e38hq!@0n64g@qe6)5csk41t=ljo2vllog(%k7njnm4b@kh42c"
export KRATOS_URL="http://127.0.0.1:8000" export KRATOS_URL="http://127.0.0.1:8000"
export HYDRA_CLIENT_ID="dashboard"
export HYDRA_CLIENT_SECRET="BrYRtKygtrcwGHviUSqybvFTgfnaZgPh"
export HYDRA_AUTHORIZATION_BASE_URL="https://sso.init.stackspin.net/oauth2/auth"
export TOKEN_URL="https://sso.init.stackspin.net/oauth2/token"
flask run flask run