|
|
@ -143,8 +143,15 @@ proc validateJWT*(ctx: ApiAuthContext, jwt: JWT) =
|
|
|
|
if jwt.claims.sub.isNone: failAuth "Missing 'sub' claim."
|
|
|
|
if jwt.claims.sub.isNone: failAuth "Missing 'sub' claim."
|
|
|
|
if jwt.claims.exp.isNone: failAuth "Missing or invalid 'exp' claim."
|
|
|
|
if jwt.claims.exp.isNone: failAuth "Missing or invalid 'exp' claim."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if jwt.claims["aud"].get.kind == JString:
|
|
|
|
|
|
|
|
# If the token is for a single audience, check that it is for us.
|
|
|
|
if not ctx.validAudiences.contains(jwt.claims.aud.get):
|
|
|
|
if not ctx.validAudiences.contains(jwt.claims.aud.get):
|
|
|
|
failAuth "JWT is not for us (invalid audience)."
|
|
|
|
failAuth "JWT is not for us (invalid audience)."
|
|
|
|
|
|
|
|
elif jwt.claims["aud"].get.kind == JArray:
|
|
|
|
|
|
|
|
# If the token is for multiple audiences, check that at least one is for us.
|
|
|
|
|
|
|
|
let auds = jwt.claims["aud"].get.getElems
|
|
|
|
|
|
|
|
if not auds.anyIt(ctx.validAudiences.contains(it.getStr)):
|
|
|
|
|
|
|
|
failAuth "JWT is not for us (invalid audience)."
|
|
|
|
|
|
|
|
|
|
|
|
let signingAlgorithm = jwt.header.alg.get
|
|
|
|
let signingAlgorithm = jwt.header.alg.get
|
|
|
|
|
|
|
|
|
|
|
@ -160,7 +167,7 @@ proc validateJWT*(ctx: ApiAuthContext, jwt: JWT) =
|
|
|
|
failAuth(getCurrentExceptionMsg(), getCurrentException())
|
|
|
|
failAuth(getCurrentExceptionMsg(), getCurrentException())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc extractValidJwt*(ctx: ApiAuthContext, req: Request): JWT =
|
|
|
|
proc extractValidJwt*(ctx: ApiAuthContext, req: Request, validateCsrf = true): JWT =
|
|
|
|
## Extracts a valid JWT representing the user's authentication and
|
|
|
|
## Extracts a valid JWT representing the user's authentication and
|
|
|
|
## authorization details, if present. If there are no valid credentials an
|
|
|
|
## authorization details, if present. If there are no valid credentials an
|
|
|
|
## exception is raised.
|
|
|
|
## exception is raised.
|
|
|
@ -184,6 +191,12 @@ proc extractValidJwt*(ctx: ApiAuthContext, req: Request): JWT =
|
|
|
|
##
|
|
|
|
##
|
|
|
|
## In the split-cookie mode we also check that the `csrfToken` claim in the
|
|
|
|
## In the split-cookie mode we also check that the `csrfToken` claim in the
|
|
|
|
## JWT payload matches the CSRF value passed via the `X-CSRF-TOKEN` header.
|
|
|
|
## JWT payload matches the CSRF value passed via the `X-CSRF-TOKEN` header.
|
|
|
|
|
|
|
|
## This CSRF check can be disabled by setting `validateCsrf` to `false`.
|
|
|
|
|
|
|
|
## This option is proivded to support occasional use-cases where you want to
|
|
|
|
|
|
|
|
## be able to serve a request using cookie auth when the client can't set
|
|
|
|
|
|
|
|
## custom headers (e.g. a simple link from an <a> tag). Obviously, this is a
|
|
|
|
|
|
|
|
## security risk and should only be used with caution with a full
|
|
|
|
|
|
|
|
## understanding of the risk.
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
if req.headers.contains("Authorization"):
|
|
|
|
if req.headers.contains("Authorization"):
|
|
|
@ -207,6 +220,7 @@ proc extractValidJwt*(ctx: ApiAuthContext, req: Request): JWT =
|
|
|
|
|
|
|
|
|
|
|
|
# Because this is a web session, check that the CSRF is present and
|
|
|
|
# Because this is a web session, check that the CSRF is present and
|
|
|
|
# matches.
|
|
|
|
# matches.
|
|
|
|
|
|
|
|
if validateCsrf:
|
|
|
|
if not req.headers.contains("X-CSRF-TOKEN") or
|
|
|
|
if not req.headers.contains("X-CSRF-TOKEN") or
|
|
|
|
not result.claims["csrfToken"].isSome:
|
|
|
|
not result.claims["csrfToken"].isSome:
|
|
|
|
failAuth "missing CSRF token"
|
|
|
|
failAuth "missing CSRF token"
|
|
|
|