Oaut2 Integrator Module
Summary
GuardianKey Auth Bastion (GKAB) requires an integration module to manage the creation and termination of sessions in the protected system.
This documentation details the integration process.
GKAB interacts with OAuth2/OIDC systems and, after authenticating a user, sends a POST request containing the necessary information to create a session in the protected system. The integrator module endpoint must return the token data to be inserted into the user's browser, as well as the URL for redirection after authentication.
The integrator module must interact with the protected system to create the session, which can be done using its API or even direct access to the session database or storage.
Integration Requirements
To perform the integration, the integrator module must meet the following requirements:
- Hosting: It must be hosted at a URL accessible by the GuardianKey Controller (example:
http://myserver/any_path
). This URL must be configured in GKAB. - Request Validation: The endpoint must validate requests using the key sent in the
X-API-Key
header. - Available Functions: The endpoint must implement two main functions:
- Login: Responsible for creating the user's session in the protected system.
- Logout: Responsible for terminating the user's session in the protected system.
Below are the expected inputs and outputs for each of these functions.
Request Authentication
All requests sent by the GuardianKey Controller include the X-API-Key
header, which contains the SHA256 hash of the API Key configured in the corresponding Auth Group.
To locate the API Key associated with your Auth Group, follow the steps below:
- Go to the GK Auth Bastion → Settings → Auth Groups menu.
- Edit the desired Auth Group.
- Locate the API Key (readonly) field and copy the displayed value.
The integrator module must calculate the SHA256 hash of this API Key to use it as the value of the X-API-Key
header. See the example below:
import hashlib
my_api_key = "your_api_key_here"
xapikey = hashlib.sha256(my_api_key.encode()).hexdigest()
Make sure the generated value is used to validate requests received by the integrator module.
Login Process
During the login step, the GuardianKey Controller sends a request to the integrator module containing the authenticated user's data. The integrator module must process this information, which includes the key "action": "login"
and the data provided by the configured OAuth2/OIDC system, to create a session in the protected system. This process ensures that the user has secure, monitored, and controlled access to the desired environment.
Request Example
Below is an example of the payload sent by the GuardianKey Controller to the integrator module to create the session in the protected application:
{
"action": "login",
"sub": "19dfab21-6eaa-4db5-bae6-c69225c2b22d",
"email_verified": false,
"name": "João Silva",
"preferred_username": "11111111111",
"given_name": "João",
"family_name": "Silva",
"email": "[email protected]",
"id_token": "",
"oauth2_token": {
"access_token": "..-",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "",
"token_type": "Bearer",
"id_token": "",
"not-before-policy": 0,
"session_state": "9f14ea07-ab00-40c4-99b7-2ab09b890f99",
"scope": "openid profile email",
"expires_at": 1747186699
}
}
Expected Response
The integrator module must return the following data:
- Authentication method: Indicates how the user's browser will be authenticated (e.g., via cookies).
- Cookie names and values: Information needed to authenticate the user's session.
- Redirection URL: Path where the user's browser will be redirected after login.
- Cookie path: Defines the scope of the cookies in the browser.
Response Example
{
"method": "cookie",
"cookie_name": ["cookie_session", "cookie_session2"],
"token": ["<generated_token>", "<value>"],
"next_url": "/home",
"cookie_path": "/"
}
Notes
- The
token
field must contain the cookie values, in the same order as in "cookie_name". - The redirection URL (
next_url
) must point to the home page or dashboard of the protected system.
Logout Process
During the logout step, the GuardianKey Controller sends a request to the integrator module containing the authenticated user's data. The integrator module must process this information, which includes the key "action": "logout"
and the data provided by the configured OAuth2/OIDC system, to terminate the session in the protected system. This process ensures that the user's access to the environment is securely and properly revoked.
Request Example
Below is an example of the payload sent by the GuardianKey Controller to the integrator module to terminate the session in the protected application:
{
"action": "logout",
"id_token": "xxx",
"sub": "19dfab21-6eaa-4db5-bae6-c69225c2b22d",
"preferred_username": "joao.silva",
"name": "João Silva",
"login_data": {
"method": "cookie",
"cookie_name": ["cookie_session", "cookie_session2"],
"token": ["<generated_token>", "<value>"],
"next_url": "/home",
"cookie_path": "/"
}
}
Expected Response
The integrator module must return the following data:
- Authentication method: Indicates how the user's browser will be deauthenticated (e.g., via cookies).
- Cookie names and values: Information needed to revoke the user's session.
- Redirection URL: Path where the user's browser will be redirected after logout.
Response Example
{
"method": "cookie",
"cookie_name": ["cookie_session", "cookie_session2"],
"next_url": "/login"
}
Notes
- The
cookie_name
field must contain the names of the cookies that need to be removed or invalidated. - The redirection URL (
next_url
) must point to the login page or another appropriate page after logout.
Exceptions
During integration, exceptions may occur that must be properly handled by the integrator module. Below is an example of a JSON response that the integrator module can return in case of an error, such as an invalid API key:
{
"error": "Invalid API key. It should be the sha256 hash of the API key you found in the panel.",
"received": "value_received_in_X-API-Key_header",
"next_url": "/login"
}
Notes
- The
error
field must clearly describe the reason for the error. - The
received
field can be used for debugging, indicating the value received in theX-API-Key
header. - The
next_url
field must point to the login page or another appropriate page for redirection after the error.
Example Code for the Integrator
from datetime import datetime
import hashlib
import sqlite3
from fastapi import FastAPI, Request
from pydantic import BaseModel
import os
secret_key = "SW2YcwTIb9zpOOhoPsMm" # Replace with your actual secret key
xapikey = "xxxxxxx" # Replace with your actual API key
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Welcome to the FastAPI API!"}
def get_user(username):
conn = sqlite3.connect("/grafana/grafana.db")
cursor = conn.cursor()
cursor.execute("SELECT id,login,email FROM user WHERE login = ?", (username,))
user = cursor.fetchone()
conn.close()
if user:
return {
"id": user[0],
"login": user[1],
"email": user[2]
}
else:
return None
def delete_session_in_db(userdb,post_data):
now = int(datetime.timestamp(datetime.now()))
conn = sqlite3.connect("/grafana/grafana.db")
cursor = conn.cursor()
hashedToken = None
try:
if "login_data" in post_data:
login_data = post_data["login_data"]
unhashedToken = login_data["token"][0] if "token" in login_data and len(login_data["token"]) > 0 else None
hashedToken = hashlib.sha256((unhashedToken + secret_key).encode()).hexdigest() if unhashedToken else None
except:
pass
if hashedToken:
cursor.execute(
"UPDATE user_auth_token SET revoked_at = ? WHERE auth_token = ?",
(now, hashedToken),
)
else:
cursor.execute(
"UPDATE user_auth_token SET revoked_at = ? WHERE user_id = ?",
(now, userdb["id"]),
)
conn.commit()
conn.close()
def create_session_in_db(userdb,post_data):
user_agent = post_data["user_agent"] if "user_agent" in post_data else ""
client_ip = post_data["client_ip"] if "client_ip" in post_data else ""
now = int(datetime.timestamp(datetime.now()))
conn = sqlite3.connect("/grafana/grafana.db")
cursor = conn.cursor()
unhashedToken = os.urandom(16).hex()
hashedToken = hashlib.sha256((unhashedToken + secret_key).encode()).hexdigest()
cursor.execute(
"INSERT INTO user_auth_token (user_id, auth_token, prev_auth_token, user_agent, client_ip, auth_token_seen, seen_at, rotated_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(userdb["id"], hashedToken, hashedToken, user_agent, client_ip, 0, now, now, now, now),
)
conn.commit()
conn.close()
return unhashedToken
@app.post("/create_session")
async def create_session(request: Request):
if request.headers["X-API-Key"] != xapikey:
return {
"error": "Invalid API key. It should be the sha256 hash of the API key you found the the panel.",
"received": request.headers["X-API-Key"],
"next_url": "/login",
}
post_data = await request.json()
if post_data["action"] == "login":
username = post_data['preferred_username'] if 'preferred_username' in post_data else None
userdb = get_user(username)
if userdb == None:
return {
"error": "User not found",
"received": post_data,
"next_url": "/login",
}
token = create_session_in_db(userdb,post_data)
timestamp_next_hour = int(datetime.timestamp(datetime.now())) + 3600
return {
"method": "cookie",
"cookie_name": ["grafana_session", "grafana_session_expiry"],
"token": [token, timestamp_next_hour],
"next_url": "/dashboards",
"cookie_path": "/",
}
elif post_data["action"] == "logout":
username = post_data['preferred_username'] if 'preferred_username' in post_data else None
userdb = get_user(username)
delete_session_in_db(userdb,post_data)
return {
"method": "cookie",
"cookie_name": ["grafana_session", "grafana_session_expiry"],
"next_url": "/login",
}
else:
return {"error": "Invalid action"}