auth, work in progress: decode id_token, + other improvements

This commit is contained in:
Helmut Merz 2025-04-25 20:34:52 +02:00
parent 87310b9798
commit 01fc7d2874
4 changed files with 30 additions and 10 deletions

View file

@ -28,7 +28,7 @@ dbpassword = getenv('DBPASSWORD', 'secret')
dbschema = getenv('DBSCHEMA', 'demo') dbschema = getenv('DBSCHEMA', 'demo')
# authentication settings # 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_client_id = getenv('OIDC_CLIENT_ID', '311613119816392525')
oidc_params = dict( oidc_params = dict(
op_config_url=oidc_provider + '/.well-known/openid-configuration', op_config_url=oidc_provider + '/.well-known/openid-configuration',

View file

@ -5,10 +5,10 @@ from scopes.storage import topic
import logging import logging
import waitress import waitress
from wsgiref.simple_server import make_server
def run(app, config): def run(app, config):
oidc.startup() # todo: use generic app.startServices()
port = int(config.server_port) port = int(config.server_port)
print(f'Serving on port {port}.') print(f'Serving on port {port}.')
waitress.serve(app, port=port) waitress.serve(app, port=port)

View file

@ -29,5 +29,5 @@ def test_app(self, config):
def test_auth(self, config): def test_auth(self, config):
from scopes.web.auth import oidc from scopes.web.auth import oidc
oidc.loadOidcProviderData() oidc.startup() # todo: use generic app.startServices()
self.assertEqual(len(config.oidc_params['op_uris']), 8) self.assertEqual(len(config.oidc_params['op_uris']), 8)

View file

@ -3,6 +3,7 @@
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from email.utils import formatdate from email.utils import formatdate
import json import json
import jwt
import logging import logging
import requests import requests
from time import time from time import time
@ -103,7 +104,6 @@ class Authenticator(DummyFolder):
return None return None
def login(self): def login(self):
loadOidcProviderData()
req = self.request req = self.request
#print('***', dir(req)) #print('***', dir(req))
state = util.rndstr() state = util.rndstr()
@ -142,7 +142,9 @@ class Authenticator(DummyFolder):
tokenUrl = self.params['op_uris']['token_endpoint'] tokenUrl = self.params['op_uris']['token_endpoint']
tokenResponse = requests.post(tokenUrl, data=args) tokenResponse = requests.post(tokenUrl, data=args)
tdata = tokenResponse.json() 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']) headers = dict(Authorization='Bearer ' + tdata['access_token'])
userInfoUrl = self.params['op_uris']['userinfo_endpoint'] userInfoUrl = self.params['op_uris']['userinfo_endpoint']
userInfo = requests.get(userInfoUrl, headers=headers) userInfo = requests.get(userInfoUrl, headers=headers)
@ -189,7 +191,6 @@ class Authenticator(DummyFolder):
cookie = self.request.getCookies().get(self.params['cookie_name']) cookie = self.request.getCookies().get(self.params['cookie_name'])
if cookie is None: if cookie is None:
return {} return {}
#raise ValueError('Missing authentication cookie')
if self.cookieCrypt: if self.cookieCrypt:
cookie = self.cookieCrypt.decrypt(cookie) cookie = self.cookieCrypt.decrypt(cookie)
#print('*** loadSession', self.params['cookie_name'], cookie) #print('*** loadSession', self.params['cookie_name'], cookie)
@ -197,6 +198,23 @@ class Authenticator(DummyFolder):
data = json.loads(cookie) data = json.loads(cookie)
return data 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) @register('auth', Root)
def authView(context, request): def authView(context, request):
@ -218,6 +236,11 @@ def logout(context, request):
return DefaultView(context, request) return DefaultView(context, request)
def startup():
loadOidcProviderData()
#app.Publication.registerBeforeTraversal(
# lambda req: req.setPrincipal(authentication.authenticate(req))
oidcProviderUris = ['authorization_endpoint', 'token_endpoint', oidcProviderUris = ['authorization_endpoint', 'token_endpoint',
'introspection_endpoint', 'userinfo_endpoint', 'introspection_endpoint', 'userinfo_endpoint',
'revocation_endpoint', 'end_session_endpoint', 'revocation_endpoint', 'end_session_endpoint',
@ -230,8 +253,5 @@ def loadOidcProviderData(force=False):
opData = requests.get(params['op_config_url']).json() opData = requests.get(params['op_config_url']).json()
for key in oidcProviderUris: for key in oidcProviderUris:
uris[key] = opData[key] 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'] params['op_keys'] = requests.get(uris['jwks_uri']).json()['keys']