From 01fc7d2874cf00cc02585313ff93fbc64a2b230a Mon Sep 17 00:00:00 2001 From: Helmut Merz Date: Fri, 25 Apr 2025 20:34:52 +0200 Subject: [PATCH] auth, work in progress: decode id_token, + other improvements --- demo/config.py | 2 +- demo/demo_server.py | 2 +- scopes/tests/tlib_web.py | 2 +- scopes/web/auth/oidc.py | 34 +++++++++++++++++++++++++++------- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/demo/config.py b/demo/config.py index fbb5224..be6d49c 100644 --- a/demo/config.py +++ b/demo/config.py @@ -28,7 +28,7 @@ dbpassword = getenv('DBPASSWORD', 'secret') dbschema = getenv('DBSCHEMA', 'demo') # authentication settings -oidc_provider = 'https://a1.cy7.de' +oidc_provider = getenv('OIDC_PROVIDER', 'https://a1.cy7.de') oidc_client_id = getenv('OIDC_CLIENT_ID', '311613119816392525') oidc_params = dict( op_config_url=oidc_provider + '/.well-known/openid-configuration', diff --git a/demo/demo_server.py b/demo/demo_server.py index 24bdd23..2e76464 100644 --- a/demo/demo_server.py +++ b/demo/demo_server.py @@ -5,10 +5,10 @@ from scopes.storage import topic import logging import waitress -from wsgiref.simple_server import make_server def run(app, config): + oidc.startup() # todo: use generic app.startServices() port = int(config.server_port) print(f'Serving on port {port}.') waitress.serve(app, port=port) diff --git a/scopes/tests/tlib_web.py b/scopes/tests/tlib_web.py index 2e095cd..3916997 100644 --- a/scopes/tests/tlib_web.py +++ b/scopes/tests/tlib_web.py @@ -29,5 +29,5 @@ def test_app(self, config): def test_auth(self, config): from scopes.web.auth import oidc - oidc.loadOidcProviderData() + oidc.startup() # todo: use generic app.startServices() self.assertEqual(len(config.oidc_params['op_uris']), 8) diff --git a/scopes/web/auth/oidc.py b/scopes/web/auth/oidc.py index 384df4f..aac705c 100644 --- a/scopes/web/auth/oidc.py +++ b/scopes/web/auth/oidc.py @@ -3,6 +3,7 @@ from cryptography.fernet import Fernet from email.utils import formatdate import json +import jwt import logging import requests from time import time @@ -103,7 +104,6 @@ class Authenticator(DummyFolder): return None def login(self): - loadOidcProviderData() req = self.request #print('***', dir(req)) state = util.rndstr() @@ -142,7 +142,9 @@ class Authenticator(DummyFolder): tokenUrl = self.params['op_uris']['token_endpoint'] tokenResponse = requests.post(tokenUrl, data=args) tdata = tokenResponse.json() - #print('*** token response', tdata) + print('*** token response', tdata) + claims = self.getIdTokenData(tdata['id_token']) + print('*** token id claims', claims) headers = dict(Authorization='Bearer ' + tdata['access_token']) userInfoUrl = self.params['op_uris']['userinfo_endpoint'] userInfo = requests.get(userInfoUrl, headers=headers) @@ -189,7 +191,6 @@ class Authenticator(DummyFolder): cookie = self.request.getCookies().get(self.params['cookie_name']) if cookie is None: return {} - #raise ValueError('Missing authentication cookie') if self.cookieCrypt: cookie = self.cookieCrypt.decrypt(cookie) #print('*** loadSession', self.params['cookie_name'], cookie) @@ -197,6 +198,23 @@ class Authenticator(DummyFolder): data = json.loads(cookie) return data + def getIdTokenData(self, token): + keyUri = self.params['op_uris']['jwks_uri'] + jwksClient = jwt.PyJWKClient(keyUri) + key = jwksClient.get_signing_key_from_jwt(token) + return jwt.decode(token, key, options=dict(verify_aud=False)) + header = jwt.get_unverified_header(token) + kid = header['kid'] + key = self.loadOidcKeys()[kid] + return jwt.decode(token, key, audience=self.params.client_id) + + def loadOidcKeys(self): + result = {} + keyUri = self.params['op_uris']['jwks_uri'] + for k in requests.get(keyUri).json()['keys']: + result[k['kid']] = jwt.PyJWK(k) + return result + @register('auth', Root) def authView(context, request): @@ -218,6 +236,11 @@ def logout(context, request): return DefaultView(context, request) +def startup(): + loadOidcProviderData() + #app.Publication.registerBeforeTraversal( + # lambda req: req.setPrincipal(authentication.authenticate(req)) + oidcProviderUris = ['authorization_endpoint', 'token_endpoint', 'introspection_endpoint', 'userinfo_endpoint', 'revocation_endpoint', 'end_session_endpoint', @@ -230,8 +253,5 @@ def loadOidcProviderData(force=False): opData = requests.get(params['op_config_url']).json() for key in oidcProviderUris: uris[key] = opData[key] - if force or params.get('op_keys') is None: + #if force or params.get('op_keys') is None: params['op_keys'] = requests.get(uris['jwks_uri']).json()['keys'] - - -