Implemented oidc with hydra
This commit is contained in:
parent
26ffb28a41
commit
2160f634d1
9 changed files with 87 additions and 16 deletions
3
app.py
3
app.py
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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})
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
from .kratos_api import *
|
from .kratos_api import *
|
||||||
from .error_handler import *
|
from .error_handler import *
|
||||||
|
from .hydra_oauth import *
|
||||||
|
|
|
@ -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
41
helpers/hydra_oauth.py
Normal 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)
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue