Skip to main content

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:

  1. Go to the GK Auth Bastion → Settings → Auth Groups menu.
  2. Edit the desired Auth Group.
  3. 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 the X-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"}