Skip to content
This repository was archived by the owner on Mar 26, 2024. It is now read-only.

Commit 906fa57

Browse files
committed
Add JWT support for user-interactive flows
1 parent 3349ae9 commit 906fa57

File tree

3 files changed

+88
-0
lines changed

3 files changed

+88
-0
lines changed

synapse/api/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class LoginType:
8484
SSO: Final = "m.login.sso"
8585
DUMMY: Final = "m.login.dummy"
8686
REGISTRATION_TOKEN: Final = "m.login.registration_token"
87+
JWT: Final = "org.matrix.login.jwt"
8788

8889

8990
# This is used in the `type` parameter for /register when called by

synapse/handlers/auth.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,10 @@ async def _get_available_ui_auth_types(self, user: UserID) -> Iterable[str]:
413413
):
414414
ui_auth_types.add(LoginType.SSO)
415415

416+
# if JWT is enabled, allow user to re-authenticate with one
417+
if self.hs.config.jwt.jwt_enabled:
418+
ui_auth_types.add(LoginType.JWT)
419+
416420
return ui_auth_types
417421

418422
def get_enabled_auth_types(self) -> Iterable[str]:

synapse/handlers/ui_auth/checkers.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from synapse.api.constants import LoginType
2222
from synapse.api.errors import Codes, LoginError, SynapseError
23+
from synapse.types import UserID
2324
from synapse.util import json_decoder
2425

2526
if TYPE_CHECKING:
@@ -314,12 +315,94 @@ async def check_auth(self, authdict: dict, clientip: str) -> Any:
314315
)
315316

316317

318+
class JwtAuthChecker(UserInteractiveAuthChecker):
319+
AUTH_TYPE = LoginType.JWT
320+
321+
def __init__(self, hs: "HomeServer"):
322+
super().__init__(hs)
323+
self.hs = hs
324+
325+
def is_enabled(self) -> bool:
326+
return bool(self.hs.config.jwt.jwt_enabled)
327+
328+
async def check_auth(self, authdict: dict, clientip: str) -> Any:
329+
token = authdict.get("token", None)
330+
if token is None:
331+
raise LoginError(
332+
403, "Token field for JWT is missing", errcode=Codes.FORBIDDEN
333+
)
334+
335+
from authlib.jose import JsonWebToken, JWTClaims
336+
from authlib.jose.errors import BadSignatureError, InvalidClaimError, JoseError
337+
338+
jwt = JsonWebToken([self.hs.config.jwt.jwt_algorithm])
339+
claim_options = {}
340+
if self.hs.config.jwt.jwt_issuer is not None:
341+
claim_options["iss"] = {
342+
"value": self.hs.config.jwt.jwt_issuer,
343+
"essential": True,
344+
}
345+
if self.hs.config.jwt.jwt_audiences is not None:
346+
claim_options["aud"] = {
347+
"values": self.hs.config.jwt.jwt_audiences,
348+
"essential": True,
349+
}
350+
351+
try:
352+
claims = jwt.decode(
353+
token,
354+
key=self.hs.config.jwt.jwt_secret,
355+
claims_cls=JWTClaims,
356+
claims_options=claim_options,
357+
)
358+
except BadSignatureError:
359+
# We handle this case separately to provide a better error message
360+
raise LoginError(
361+
403,
362+
"JWT validation failed: Signature verification failed",
363+
errcode=Codes.FORBIDDEN,
364+
)
365+
except JoseError as e:
366+
# A JWT error occurred, return some info back to the client.
367+
raise LoginError(
368+
403,
369+
"JWT validation failed: %s" % (str(e),),
370+
errcode=Codes.FORBIDDEN,
371+
)
372+
373+
try:
374+
claims.validate(leeway=120) # allows 2 min of clock skew
375+
376+
# Enforce the old behavior which is rolled out in productive
377+
# servers: if the JWT contains an 'aud' claim but none is
378+
# configured, the login attempt will fail
379+
if claims.get("aud") is not None:
380+
if (
381+
self.hs.config.jwt.jwt_audiences is None
382+
or len(self.hs.config.jwt.jwt_audiences) == 0
383+
):
384+
raise InvalidClaimError("aud")
385+
except JoseError as e:
386+
raise LoginError(
387+
403,
388+
"JWT validation failed: %s" % (str(e),),
389+
errcode=Codes.FORBIDDEN,
390+
)
391+
392+
user = claims.get(self.hs.config.jwt.jwt_subject_claim, None)
393+
if user is None:
394+
raise LoginError(403, "Invalid JWT", errcode=Codes.FORBIDDEN)
395+
396+
return UserID(user, self.hs.hostname).to_string()
397+
398+
317399
INTERACTIVE_AUTH_CHECKERS: Sequence[Type[UserInteractiveAuthChecker]] = [
318400
DummyAuthChecker,
319401
TermsAuthChecker,
320402
RecaptchaAuthChecker,
321403
EmailIdentityAuthChecker,
322404
MsisdnAuthChecker,
323405
RegistrationTokenAuthChecker,
406+
JwtAuthChecker,
324407
]
325408
"""A list of UserInteractiveAuthChecker classes"""

0 commit comments

Comments
 (0)