feat: Enhance NetID OAuth integration with token validation and configuration options

This commit is contained in:
Christian Krakau-Louis
2025-04-15 16:17:29 +02:00
parent 6d796c95ff
commit ee30b88f91
4 changed files with 57 additions and 2 deletions
+2
View File
@@ -66,6 +66,8 @@ AUTHENTIK_CONFIG_URL=https://authentik.example.com/application/o/leagueledger/.w
# NetID # NetID
NETID_CLIENT_ID=leagueledger-netid-client NETID_CLIENT_ID=leagueledger-netid-client
NETID_CLIENT_SECRET=netid-client-secret-abcdef1234567890 NETID_CLIENT_SECRET=netid-client-secret-abcdef1234567890
NETID_TOKEN_SIGNING_ALG=RS256
NETID_VALIDATE_TOKENS=true
# Feature Flags # Feature Flags
ENABLE_REGISTRATION=True ENABLE_REGISTRATION=True
+2
View File
@@ -64,6 +64,8 @@ AUTHENTIK_CONFIG_URL=
# NetID # NetID
NETID_CLIENT_ID= NETID_CLIENT_ID=
NETID_CLIENT_SECRET= NETID_CLIENT_SECRET=
NETID_TOKEN_SIGNING_ALG=RS256
NETID_VALIDATE_TOKENS=true
# Feature Flags # Feature Flags
ENABLE_REGISTRATION=True ENABLE_REGISTRATION=True
+52 -2
View File
@@ -830,10 +830,12 @@ class NetIDOAuth(OAuthProvider):
AUTHORIZATION_URL = "https://broker.netid.de/authorize" AUTHORIZATION_URL = "https://broker.netid.de/authorize"
TOKEN_URL = "https://broker.netid.de/token" TOKEN_URL = "https://broker.netid.de/token"
USERINFO_URL = "https://broker.netid.de/userinfo" USERINFO_URL = "https://broker.netid.de/userinfo"
JWKS_URL = "https://broker.netid.de/jwks" # JWKS endpoint for token validation
def __init__(self): def __init__(self):
self.client_id = os.getenv("NETID_CLIENT_ID", "") self.client_id = os.getenv("NETID_CLIENT_ID", "")
self.client_secret = os.getenv("NETID_CLIENT_SECRET", "") self.client_secret = os.getenv("NETID_CLIENT_SECRET", "")
self.token_signing_alg = os.getenv("NETID_TOKEN_SIGNING_ALG", "RS256")
super().__init__() super().__init__()
def initialize_client(self): def initialize_client(self):
@@ -864,10 +866,17 @@ class NetIDOAuth(OAuthProvider):
raise HTTPException(status_code=500, detail="NetID OAuth client could not be initialized") raise HTTPException(status_code=500, detail="NetID OAuth client could not be initialized")
try: try:
# Add token signing algorithm parameter to the authorization request
extras_params = {
"response_type": "code",
"id_token_signed_response_alg": self.token_signing_alg
}
authorization_url = await self.client.get_authorization_url( authorization_url = await self.client.get_authorization_url(
redirect_uri=redirect_uri, redirect_uri=redirect_uri,
scope=["openid", "email", "profile"], scope=["openid", "email", "profile"],
state=str(request.session.get("session_id", "")) state=str(request.session.get("session_id", "")),
extras_params=extras_params
) )
return authorization_url return authorization_url
except Exception as e: except Exception as e:
@@ -883,16 +892,26 @@ class NetIDOAuth(OAuthProvider):
try: try:
# Exchange code for token # Exchange code for token
token_params = {
"id_token_signed_response_alg": self.token_signing_alg
}
token = await self.client.get_access_token( token = await self.client.get_access_token(
code=code, code=code,
redirect_uri=redirect_uri redirect_uri=redirect_uri,
extra_params=token_params
) )
access_token = token.get("access_token") access_token = token.get("access_token")
id_token = token.get("id_token") # JWT containing identity information
if not access_token: if not access_token:
raise HTTPException(status_code=400, detail="Could not get NetID access token") raise HTTPException(status_code=400, detail="Could not get NetID access token")
# If we have an ID token, validate its signature when configured to do so
if id_token and os.getenv("NETID_VALIDATE_TOKENS", "true").lower() == "true":
await self.validate_id_token(id_token)
# Get user info from NetID UserInfo endpoint # Get user info from NetID UserInfo endpoint
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
headers = {"Authorization": f"Bearer {access_token}"} headers = {"Authorization": f"Bearer {access_token}"}
@@ -913,6 +932,37 @@ class NetIDOAuth(OAuthProvider):
print(f"Error getting NetID user info: {str(e)}") print(f"Error getting NetID user info: {str(e)}")
raise HTTPException(status_code=500, detail=f"OAuth error: {str(e)}") raise HTTPException(status_code=500, detail=f"OAuth error: {str(e)}")
async def validate_id_token(self, id_token: str) -> None:
"""Validate the NetID ID token signature using JWKS"""
try:
import jwt
from jwt.jwks_client import PyJWKClient
# Create a JWKS client to fetch the public keys from NetID
jwks_client = PyJWKClient(self.JWKS_URL)
# Get the signing key for this specific JWT
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
# Verify the JWT using the fetched public key
# This will raise exceptions if the token is invalid
jwt.decode(
id_token,
signing_key.key,
algorithms=[self.token_signing_alg],
audience=self.client_id,
options={"verify_exp": True}
)
# If we get here, the token is valid
return True
except Exception as e:
print(f"Error validating NetID ID token: {str(e)}")
# In production, you might want to raise an exception here
# For now, we'll just log the error but not block the flow
return False
def get_normalized_user_data(self, user_info: Dict[str, Any]) -> Dict[str, Any]: def get_normalized_user_data(self, user_info: Dict[str, Any]) -> Dict[str, Any]:
# NetID OpenID Connect response normalization # NetID OpenID Connect response normalization
return { return {
+1
View File
@@ -16,6 +16,7 @@ python-multipart>=0.0.6
passlib>=1.7.4 passlib>=1.7.4
itsdangerous>=2.1.2 itsdangerous>=2.1.2
bcrypt>=4.0.1 bcrypt>=4.0.1
PyJWT>=2.6.0 # Added for NetID token validation
# OAuth client # OAuth client
httpx-oauth>=0.10.0 httpx-oauth>=0.10.0