401 : Unauthorized Audience doesn't match

Hi,

I deployed jupyterhub with z2jh k8s with LTI1.3 authentication and it was working fine. But, after upgrading my server, I keep getting this error

401 : Unauthorized
Audience doesn't match

I was wondering how I can fix it?

best

Can you turn on debug logging, and show us your hub logs? It’ll also be helpful to see your Z2JH configuration.

1 Like

Thanks for your response. I get the following line in the log:

[W 2024-04-12 13:30:08.775 JupyterHub web:1869] 401 POST /hub/lti13/oauth_callback (10.42.0.1): Audience doesn't match

and here is the relevant part of my configuration (LTI1.3 part):


hub:
    cookieSecret: redacted
    config:
      Authenticator:
        enable_auth_state: true
        auto_login: False
...
 # # lti authentication
      lti13-auth: |

        import re
        from ltiauthenticator.lti13.auth import LTI13Authenticator
        class MyCustomAuthenticator(LTI13Authenticator):
          @classmethod
          def normalize_username(cls, username):
            return re.sub(r"[@.]+", "-", username)
        c.JupyterHub.authenticator_class = MyCustomAuthenticator
        c.LTI13Authenticator.issuer = redacted
        c.LTI13Authenticator.authorize_url = redacted
        c.LTI13Authenticator.jwks_endpoint = redacted
        c.LTI13Authenticator.client_id = redacted
        c.LTI13Authenticator.username_key = "email"

The same script used to work but now it doesn’t for some reasons. In the developer tools, I get a 401 oauth_callback error. what’s the problem? Is there anything wrong with the LTI1.3 credentials provider? or is it coming from jupyterhub? where should I search to fix it?

best,

This might be relevant as well: I have the following security headers in my script:

ingress:
  annotations:
      nginx.ingress.kubernetes.io/ssl-redirect: "true"
      # nginx.ingress.kubernetes.io/http2-max-age: "31557600"
      nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
      nginx.ingress.kubernetes.io/configuration-snippet: |
        add_header Strict-Transport-Security "max-age=31557600; includeSubDomains; preload" always;
        add_header X-Content-Type-Options nosniff always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-XSS-Protection "1; mode=block" always;
        add_header X-Robots-Tag "noindex, nofollow" always;
        add_header X-Download-Options noopen always;
        add_header X-Permitted-Cross-Domain-Policies none always;
        add_header Referrer-Policy same-origin always;
        add_header Permissions-Policy "microphone=(), geolocation=(), camera=()" always;

That’s only a single line, which shows the request failed. If you turn on debugging logging you should see a lot more log messages that may give some relevant context- can you share those?

1 Like

Here is the full feedback. When I decode the initial launch args id_token in jwt.io, all information about the user is correct, nevertheless I get 401 audience doesn’t match error.


[D 2024-04-22 07:22:43.324 JupyterHub handlers:259] Initial login request args are {'iss': 'https://mydomain', 'target_link_uri': 'http://mydomain', 'login_hint': '13457', 'lti_message_hint': '{"cmid":4631964,"launchid":"ltilaunch2531_1318898034"}', 'client_id': 'redacted', lti_deployment_id': '16'}
[D 2024-04-22 07:22:43.324 JupyterHub handlers:268] login_hint is 13457
[D 2024-04-22 07:22:43.324 JupyterHub handlers:333] lti_message_hint is {"cmid":4631964,"launchid":"ltilaunch2531_1318898034"}
[D 2024-04-22 07:22:43.324 JupyterHub handlers:333] client_id is redacted
[D 2024-04-22 07:22:43.324 JupyterHub handlers:279] redirect_uri is: https://mydomain/hub/lti13/oauth_callback
[W 2024-04-22 07:22:43.324 JupyterHub handlers:243] Ignoring next_url None, using '/'
[D 2024-04-22 07:22:43.324 JupyterHub base:587] Setting cookie lti13authenticator-state: {'httponly': True, 'secure': True, 'expires_days': 1}
[D 2024-04-22 07:22:43.325 JupyterHub base:587] Setting cookie lti13authenticator-nonce-state: {'httponly': True, 'secure': True, 'expires_days': 1}
[D 2024-04-22 07:22:43.325 JupyterHub handlers:286] nonce value: redacted
[I 2024-04-22 07:22:43.326 JupyterHub log:191] 302 POST /hub/lti13/oauth_login -> https://mydomain/mod/lti/auth.php?response_type=id_token&scope=openid&response_mode=form_post&prompt=none&client_id=redacted&redirect_uri=https%3A%2F%2Fmydomain%2Fhub%2Flti13%2Foauth_callback&login_hint=13457&nonce=redacted&state=[secret]&lti_message_hint=%7B%22cmid%22%3A4631964%2C%22launchid%22%3A%22ltilaunch2531_1318898034%22%7D (@10.42.0.1) 1.95ms
[D 2024-04-22 07:22:43.850 JupyterHub handlers:416] Initial launch request args are {'id_token': 'Redacted', 'state': 'redacted'}
[W 2024-04-22 07:22:43.948 JupyterHub web:1869] 401 POST /hub/lti13/oauth_callback (10.42.0.1): Audience doesn't match
[D 2024-04-22 07:22:43.948 JupyterHub base:1371] No template for 401
[W 2024-04-22 07:22:43.949 JupyterHub log:191] 401 POST /hub/lti13/oauth_callback (@10.42.0.1) 99.49ms

I think this is where the token is validated:

Can you try manually (e.g. in a notebook or a Python shell) running the above verification code with your token?

I tried this piece of code and it worked.

# lti credentials
issuer = "redacted"
authorize_url = "redacted"
jwks_endpoint = "redacted"
client_id = "redacted"
username_key = 'email'

# other params
encoded_jwt = "redacted"
verification_options = {"verify_signature": True}
jwks_algorithms = ["redacted"] 
audience = "redacted"

import jwt 

try:
    if verification_options["verify_signature"]:
        jwks_client = jwt.PyJWKClient(jwks_endpoint)
        signing_key = jwks_client.get_signing_key_from_jwt(encoded_jwt)
        key = signing_key.key
    else:
        key = ""
    
    id_token = jwt.decode(
        encoded_jwt,
        key,
        algorithms=jwks_algorithms,
        audience=audience,
        issuer=issuer,
        options=verification_options,
        leeway=5,
    )

except Exception as e:
    raise(e)

id_token contains all my information.

I said it works in previous message. But I meant, the python code you suggested correctly decodes the token but i still get the same error in jupyterhub (401 audience doesn’t match).