Coverage for /usr/local/lib/python3.10/site-packages/opal_common-0.0.0-py3.10.egg/opal_common/authentication/verifier.py: 66%
49 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 11:54 +0000
1from typing import Optional
3import jwt
4from fastapi import HTTPException, status
5from jwt.algorithms import Algorithm, get_default_algorithms
6from jwt.api_jwk import PyJWK
7from opal_common.authentication.types import JWTAlgorithm, JWTClaims, PublicKey
8from opal_common.logger import logger
11class Unauthorized(HTTPException):
12 """HTTP 401 Unauthorized exception."""
14 def __init__(self, description="Bearer token is not valid!", **kwargs):
15 super().__init__(
16 status_code=status.HTTP_401_UNAUTHORIZED,
17 detail={"error": description, **kwargs},
18 headers={"WWW-Authenticate": "Bearer"},
19 )
22class JWTVerifier:
23 """Given a cryptographic public key, can verify jwt tokens."""
25 def __init__(
26 self,
27 public_key: Optional[PublicKey],
28 algorithm: JWTAlgorithm,
29 audience: str,
30 issuer: str,
31 ):
32 """Inits the signer if and only if the keys provided to __init__ were
33 generate together are are valid. otherwise will throw.
35 JWT verifier can be initialized without a public key (None)
36 in which case verifier.enabled == False and jwt verification is turned off.
38 This allows opal to run both in secure mode (with jwt-based authentication)
39 and in insecure mode (intended for development environments and running locally).
41 Args:
42 public_key (PublicKey): a valid public key or None
43 algorithm (JWTAlgorithm): the jwt algorithm to use
44 (possible values: https://pyjwt.readthedocs.io/en/stable/algorithms.html)
45 audience (string): the value for the aud claim: https://tools.ietf.org/html/rfc7519#section-4.1.3
46 issuer (string): the value for the iss claim: https://tools.ietf.org/html/rfc7519#section-4.1.1
47 """
48 self._public_key = public_key
49 self._algorithm: str = algorithm.value
50 self._audience = audience
51 self._issuer = issuer
52 self._enabled = True
53 self._verify_public_key()
55 def _verify_public_key(self):
56 """Verifies whether or not the public key is a valid crypto key
57 (according to the JWT algorithm)."""
58 if self._public_key is not None: 58 ↛ 68line 58 didn't jump to line 68 because the condition on line 58 was always true
59 # save jwk
60 try:
61 self._jwk: PyJWK = PyJWK.from_json(
62 self.get_jwk(), algorithm=self._algorithm
63 )
64 except jwt.exceptions.InvalidKeyError as e:
65 logger.error(f"Invalid public key for jwt verification, error: {e}!")
66 self._disable()
67 else:
68 self._disable()
70 def get_jwk(self) -> str:
71 """Returns the jwk json contents."""
72 algorithm: Optional[Algorithm] = get_default_algorithms().get(self._algorithm)
73 if algorithm is None: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true
74 raise ValueError(f"invalid jwt algorithm: {self._algorithm}")
75 return algorithm.to_jwk(self._public_key)
77 def _disable(self):
78 self._enabled = False
80 @property
81 def enabled(self):
82 """Whether or not the verifier has valid cryptographic keys."""
83 return self._enabled
85 def verify(self, token: str) -> JWTClaims:
86 """Verifies a JWT token is valid.
88 if valid returns dict with jwt claims, otherwise throws.
89 """
90 try:
91 return jwt.decode(
92 token,
93 self._public_key,
94 algorithms=[self._algorithm],
95 audience=self._audience,
96 issuer=self._issuer,
97 )
98 except jwt.ExpiredSignatureError:
99 raise Unauthorized(token=token, description="Access token is expired")
100 except jwt.InvalidAudienceError:
101 raise Unauthorized(
102 token=token, description="Invalid access token: invalid audience claim"
103 )
104 except jwt.InvalidIssuerError:
105 raise Unauthorized(
106 token=token, description="Invalid access token: invalid issuer claim"
107 )
108 except jwt.DecodeError:
109 raise Unauthorized(token=token, description="Could not decode access token")
110 except Exception:
111 raise Unauthorized(token=token, description="Unknown JWT error")