mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
lws_jwt_token_sanity
This commit is contained in:
parent
1db26f0c64
commit
f1f34a7d4b
6 changed files with 527 additions and 3 deletions
176
READMEs/README.jwt.md
Normal file
176
READMEs/README.jwt.md
Normal file
|
@ -0,0 +1,176 @@
|
|||
# JWT support in lws
|
||||
|
||||
lws supports the common usage scenarios of JWS (signed) JWT generation,
|
||||
parsing and transferring in and out as http cookies. Care is taken to provide
|
||||
helpers that implement the current security best practices for cookie handling
|
||||
and JWT validation. All of the common algorithms like ES512 are supported
|
||||
along with JWK generation and handling apis.
|
||||
|
||||
The build options needed are `-DLWS_WITH_JOSE=1` `-DLWS_WITH_GENCRYPTO=1`.
|
||||
|
||||
Underlying JOSE primitives are exposed as apis, some JWT specific primitives
|
||||
and finally a JWT-via http cookie level creation apis each building on top of
|
||||
what was underneath.
|
||||
|
||||
The higher level APIs are provided additionally because they have the most
|
||||
opportunity for implementation pitfalls like not validating alg carefully, or
|
||||
not using the latest cookie security options; the provided APIs handle that
|
||||
centrally for you. If your needs vary from what the higher level apis are
|
||||
doing, you can cut-and-paste out those implementations and create your own
|
||||
using the public lower level apis.
|
||||
|
||||
## LWS JWT fields
|
||||
|
||||
Lws JWT uses mainly well-known fields
|
||||
|
||||
Field|Std|Meaning
|
||||
---|---|---
|
||||
iss|yes|Issuer, typically the domain like "warmcat.com"
|
||||
aud|yes|Audience, typically a url path like "https://warmcat.com/sai"
|
||||
iat|yes|Unix-time "Issued At"
|
||||
nbf|yes|Unix-time "Not Before"
|
||||
exp|yes|Unix-time "Expired"
|
||||
sub|yes|Subject, eg, a username or user email
|
||||
csrf|no|A random 16-char hex token generated with the JWT for use in links specific to the JWT bearer
|
||||
ext|no|Application-specific JSON sub-object with whatever fields you need, eg, `"authorization": 1`
|
||||
|
||||
## Approach for JWT as session token
|
||||
|
||||
Once JWTs are produced, they are autonomous bearer tokens, if they are not kept
|
||||
secret between the browser and the site, they will be accepted as evidence for
|
||||
having rights to the session from anyone.
|
||||
|
||||
Requiring https, and various other cookie hardening techniques make it more
|
||||
difficult for them to leak, but it is still necessary to strictly constrain the
|
||||
token's validity time, usually to a few tens of minutes or how long it takes a
|
||||
user to login and get stuff done on the site in one session.
|
||||
|
||||
## CSRF mitigation
|
||||
|
||||
Cross Site Request Forgery (CSRF) is a hacking scenario where an authorized
|
||||
user with a valid token is tricked into clicking on an external link that
|
||||
performs some action with side-effects on the site he has active auth on. For
|
||||
example, he has a cookie that's logged into his bank, and the link posts a form
|
||||
to the bank site transferring money to the attacker.
|
||||
|
||||
Lws JWT mitigates this possibility by putting a random secret in the generated
|
||||
JWT; when the authorized user presents his JWT to generate the page, generated
|
||||
links that require auth to perform their actions include the CSRF string from
|
||||
that user's current JWT.
|
||||
|
||||
When the user clicks those links intentionally, the CSRF string in the link
|
||||
matches the CSRF string in the currently valid JWT that was also provided to
|
||||
the server along with the click, and all is well.
|
||||
|
||||
An attacker does not know the random, ephemeral JWT CSRF secret to include in
|
||||
forged links, so the attacker-controlled action gets rejected at the server as
|
||||
having used an invalid link.
|
||||
|
||||
The checking and link manipulation need to be implemented in user code / JS...
|
||||
lws JWT provides the random CSRF secret in the JWT and makes it visible to the
|
||||
server when the incoming JWT is processed.
|
||||
|
||||
## Need for client tracking of short JWT validity times
|
||||
|
||||
Many links or references on pages do not require CSRF strings, only those that
|
||||
perform actions with side-effects like deletion or money transfer should need
|
||||
protecting this way.
|
||||
|
||||
Due to CSRF mitigation, generated pages containing the protected links
|
||||
effectively have an expiry time linked to that of the JWT, since only the bearer
|
||||
of the JWT used to generate the links on the page can use them; once that
|
||||
expires actually nobody can use them and the page contents, which may anyway
|
||||
be showing content that only authenticated users can see must be invalidated and
|
||||
re-fetched. Even if the contents are visible without authentication, additional
|
||||
UI elements like delete buttons that should only be shown when authenticated
|
||||
will wrongly still be shown
|
||||
|
||||
For that reason, the client should be informed by the server along with the
|
||||
authentication status, the expiry time of it. The client should then by itself
|
||||
make arrangements to refresh the page when this time is passed,
|
||||
either showing an unauthenticated version of the same page if it exists, or by
|
||||
redirecting to the site homepage if showing any of the contents required
|
||||
authentication. The user can then log back in using his credientials typically
|
||||
stored in the browser's password store and receive a new short-term JWT with a
|
||||
new random csrf token along with a new page using the new csrf token in its
|
||||
links.
|
||||
|
||||
## Considerations for long-lived connections
|
||||
|
||||
Once established as authorized, websocket links may be very long-lived and hold
|
||||
their authorization state at the server. Although the browser monitoring the
|
||||
JWT reloading the page on auth expiry should mitigate this, an attacker can
|
||||
choose to just not do that and have an immortally useful websocket link.
|
||||
|
||||
At least for actions on the long-lived connection, it should not only confirm
|
||||
the JWT authorized it but that the current time is still before the "exp" time
|
||||
in the JWT, this is made available as `expiry_unix_time` in the args struct
|
||||
after successful validation.
|
||||
|
||||
Ideally the server should close long-lived connections according to their auth
|
||||
expiry time.
|
||||
|
||||
## JWT lower level APIs
|
||||
|
||||
The related apis are in `./include/libwebsockets/lws-jws.h`
|
||||
|
||||
### Validation of JWT
|
||||
|
||||
```
|
||||
int
|
||||
lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk,
|
||||
const char *alg_list, const char *com, size_t len,
|
||||
char *temp, int tl, char *out, size_t *out_len);
|
||||
```
|
||||
|
||||
### Composing and signing JWT
|
||||
|
||||
```
|
||||
int
|
||||
lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk,
|
||||
const char *alg, char *out, size_t *out_len, char *temp,
|
||||
int tl, const char *format, ...);
|
||||
```
|
||||
|
||||
## JWT creation and cookie get / set API
|
||||
|
||||
Both the validation and signing apis use the same struct to contain their
|
||||
aguments.
|
||||
|
||||
```
|
||||
struct lws_jwt_sign_set_cookie {
|
||||
struct lws_jwk *jwk;
|
||||
/**< entry: required signing key */
|
||||
const char *alg;
|
||||
/**< entry: required signing alg, eg, "ES512" */
|
||||
const char *iss;
|
||||
/**< entry: issuer name to use */
|
||||
const char *aud;
|
||||
/**< entry: audience */
|
||||
const char *cookie_name;
|
||||
/**< entry: the name of the cookie */
|
||||
char sub[33];
|
||||
/**< sign-entry, validate-exit: subject */
|
||||
const char *extra_json;
|
||||
/**< sign-entry, validate-exit:
|
||||
* optional "ext" JSON object contents for the JWT */
|
||||
size_t extra_json_len;
|
||||
/**< validate-exit:
|
||||
* length of optional "ext" JSON object contents for the JWT */
|
||||
const char *csrf_in;
|
||||
/**< validate-entry:
|
||||
* NULL, or an external CSRF token to check against what is in the JWT */
|
||||
unsigned long expiry_unix_time;
|
||||
/**< sign-entry: seconds the JWT and cookie may live,
|
||||
* validate-exit: expiry unix time */
|
||||
};
|
||||
|
||||
int
|
||||
lws_jwt_sign_token_set_http_cookie(struct lws *wsi,
|
||||
const struct lws_jwt_sign_set_cookie *i,
|
||||
uint8_t **p, uint8_t *end);
|
||||
int
|
||||
lws_jwt_get_http_cookie_validate_jwt(struct lws *wsi,
|
||||
struct lws_jwt_sign_set_cookie *i,
|
||||
char *out, size_t *out_len);
|
||||
```
|
|
@ -454,4 +454,106 @@ LWS_VISIBLE LWS_EXTERN int
|
|||
lws_jwt_sign_compact(struct lws_context *ctx, struct lws_jwk *jwk,
|
||||
const char *alg, char *out, size_t *out_len, char *temp,
|
||||
int tl, const char *format, ...) LWS_FORMAT(8);
|
||||
|
||||
/**
|
||||
* lws_jwt_token_sanity() - check a validated jwt payload for sanity
|
||||
*
|
||||
* \param in: the JWT payload
|
||||
* \param in_len: the length of the JWT payload
|
||||
* \param iss: the expected issuer of the token
|
||||
* \param aud: the expected audience of the token
|
||||
* \param csrf_in: NULL, or the csrf token that came in on a URL
|
||||
* \param sub: a buffer to hold the subject name in the JWT (eg, account name)
|
||||
* \param sub_len: the max length of the sub buffer
|
||||
* \param secs_left: set to the number of seconds of valid auth left if valid
|
||||
*
|
||||
* This performs some generic sanity tests on validated JWT payload...
|
||||
*
|
||||
* - the issuer is as expected
|
||||
* - the audience is us
|
||||
* - current time is OK for nbf ("not before") in the token
|
||||
* - current time is OK for exp ("expiry") in the token
|
||||
* - if csrf_in is not NULL, that the JWK has a csrf and it matches it
|
||||
* - if sub is not NULL, that the JWK provides a subject (and copies it to sub)
|
||||
*
|
||||
* If the tests pass, *secs_left is set to the number of remaining seconds the
|
||||
* auth is valid.
|
||||
*
|
||||
* Returns 0 if no inconsistency, else nonzero.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_jwt_token_sanity(const char *in, size_t in_len,
|
||||
const char *iss, const char *aud, const char *csrf_in,
|
||||
char *sub, size_t sub_len, unsigned long *exp_unix_time);
|
||||
|
||||
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
|
||||
|
||||
struct lws_jwt_sign_set_cookie {
|
||||
struct lws_jwk *jwk;
|
||||
/**< entry: required signing key */
|
||||
const char *alg;
|
||||
/**< entry: required signing alg, eg, "ES512" */
|
||||
const char *iss;
|
||||
/**< entry: issuer name to use */
|
||||
const char *aud;
|
||||
/**< entry: audience */
|
||||
const char *cookie_name;
|
||||
/**< entry: the name of the cookie */
|
||||
char sub[33];
|
||||
/**< sign-entry, validate-exit: subject */
|
||||
const char *extra_json;
|
||||
/**< sign-entry, validate-exit:
|
||||
* optional "ext" JSON object contents for the JWT */
|
||||
size_t extra_json_len;
|
||||
/**< validate-exit:
|
||||
* length of optional "ext" JSON object contents for the JWT */
|
||||
const char *csrf_in;
|
||||
/**< validate-entry:
|
||||
* NULL, or an external CSRF token to check against what is in the JWT */
|
||||
unsigned long expiry_unix_time;
|
||||
/**< sign-entry: seconds the JWT and cookie may live,
|
||||
* validate-exit: expiry unix time */
|
||||
};
|
||||
|
||||
/**
|
||||
* lws_jwt_sign_token_set_cookie() - creates sets a JWT in a wsi cookie
|
||||
*
|
||||
* \param wsi: the wsi to create the cookie header on
|
||||
* \param i: structure describing what should be in the JWT
|
||||
* \param p: wsi headers area
|
||||
* \param end: end of wsi headers area
|
||||
*
|
||||
* Creates a JWT specified \p i, and attaches it to the outgoing headers on
|
||||
* wsi. Returns 0 if successful.
|
||||
*
|
||||
* Best-practice security restrictions are applied to the cookie set action,
|
||||
* including forcing httponly, and __Host- prefix. As required by __Host-, the
|
||||
* cookie Path is set to /. __Host- is applied by the function, the cookie_name
|
||||
* should just be "xyz" for "__Host-xyz".
|
||||
*
|
||||
* \p extra_json should just be the bare JSON, a { } is provided around it by
|
||||
* the function if it's non-NULL. For example, "\"authorization\": 1".
|
||||
*
|
||||
* It's recommended the secs parameter is kept as small as consistent with one
|
||||
* user session on the site if possible, eg, 10 minutes or 20 minutes. At the
|
||||
* server, it can determine how much time is left in the auth and inform the
|
||||
* client; if the JWT validity expires, the page should reload so the UI always
|
||||
* reflects what's possible to do with the authorization state correctly. If
|
||||
* the JWT expires, the user can log back in using credentials usually stored in
|
||||
* the browser and auto-filled-in, so this is not very inconvenient.
|
||||
*
|
||||
* This is a helper on top of the other JOSE and JWT apis that somewhat crosses
|
||||
* over between JWT and HTTP, since it knows about cookies. So it is only built
|
||||
* if both LWS_WITH_JOSE and one of the http-related roles enabled.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_jwt_sign_token_set_http_cookie(struct lws *wsi,
|
||||
const struct lws_jwt_sign_set_cookie *i,
|
||||
uint8_t **p, uint8_t *end);
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_jwt_get_http_cookie_validate_jwt(struct lws *wsi,
|
||||
struct lws_jwt_sign_set_cookie *i,
|
||||
char *out, size_t *out_len);
|
||||
#endif
|
||||
|
||||
///@}
|
||||
|
|
|
@ -261,6 +261,26 @@ lws_json_simple_strcmp(const char *buf, size_t len, const char *name, const char
|
|||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_hex_to_byte_array(const char *h, uint8_t *dest, int max);
|
||||
|
||||
|
||||
/**
|
||||
* lws_hex_random(): generate len - 1 or - 2 characters of random ascii hex
|
||||
*
|
||||
* \param context: the lws_context used to get the random
|
||||
* \param dest: destination for hex ascii chars
|
||||
* \param len: the number of bytes the buffer dest points to can hold
|
||||
*
|
||||
* This creates random ascii-hex strings up to a given length, with a
|
||||
* terminating NUL. Hex characters are produced in pairs, if the length of
|
||||
* the destination buffer is even, after accounting for the NUL there will be
|
||||
* an unused byte at the end after the NUL. So lengths should be odd to get
|
||||
* length - 1 characters exactly followed by the NUL.
|
||||
*
|
||||
* There will not be any characters produced that are not 0-9, a-f, so it's
|
||||
* safe to go straight into, eg, JSON.
|
||||
*/
|
||||
LWS_VISIBLE LWS_EXTERN int
|
||||
lws_hex_random(struct lws_context *context, char *dest, size_t len);
|
||||
|
||||
/*
|
||||
* lws_timingsafe_bcmp(): constant time memcmp
|
||||
*
|
||||
|
|
|
@ -153,6 +153,27 @@ lws_hex_to_byte_array(const char *h, uint8_t *dest, int max)
|
|||
return lws_ptr_diff(dest, odest);
|
||||
}
|
||||
|
||||
static char *hexch = "0123456789abcdef";
|
||||
|
||||
int
|
||||
lws_hex_random(struct lws_context *context, char *dest, size_t len)
|
||||
{
|
||||
size_t n = (len - 1) / 2;
|
||||
uint8_t b, *r = (uint8_t *)dest + len - n;
|
||||
|
||||
if (lws_get_random(context, r, n) != n)
|
||||
return 1;
|
||||
|
||||
while (n--) {
|
||||
b = *r++;
|
||||
*dest++ = hexch[b >> 4];
|
||||
*dest++ = hexch[b & 0xf];
|
||||
}
|
||||
|
||||
*dest = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(LWS_PLAT_OPTEE)
|
||||
|
||||
|
|
|
@ -957,9 +957,9 @@ lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk,
|
|||
{
|
||||
struct lws_tokenize ts;
|
||||
struct lws_jose jose;
|
||||
int otl = tl, r = 1;
|
||||
struct lws_jws jws;
|
||||
size_t n, r = 1;
|
||||
int otl = tl;
|
||||
size_t n;
|
||||
|
||||
memset(&jws, 0, sizeof(jws));
|
||||
lws_jose_init(&jose);
|
||||
|
@ -969,7 +969,8 @@ lws_jwt_signed_validate(struct lws_context *ctx, struct lws_jwk *jwk,
|
|||
* blocks
|
||||
*/
|
||||
|
||||
n = lws_jws_compact_decode(com, len, &jws.map, &jws.map_b64, temp, &tl);
|
||||
n = lws_jws_compact_decode(com, (int)len, &jws.map, &jws.map_b64,
|
||||
temp, &tl);
|
||||
if (n != 3) {
|
||||
lwsl_err("%s: concat_map failed: %d\n", __func__, (int)n);
|
||||
goto bail;
|
||||
|
@ -1146,3 +1147,90 @@ bail:
|
|||
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
lws_jwt_token_sanity(const char *in, size_t in_len,
|
||||
const char *iss, const char *aud,
|
||||
const char *csrf_in,
|
||||
char *sub, size_t sub_len, unsigned long *expiry_unix_time)
|
||||
{
|
||||
unsigned long now = lws_now_secs(), exp;
|
||||
const char *cp;
|
||||
size_t len;
|
||||
|
||||
/*
|
||||
* It has our issuer?
|
||||
*/
|
||||
|
||||
if (lws_json_simple_strcmp(in, in_len, "\"iss\":", iss)) {
|
||||
lwsl_notice("%s: iss mismatch\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ... it is indended for us to consume? (this is set
|
||||
* to the public base url for this sai instance)
|
||||
*/
|
||||
if (lws_json_simple_strcmp(in, in_len, "\"aud\":", aud)) {
|
||||
lwsl_notice("%s: aud mismatch\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ...it's not too early for it?
|
||||
*/
|
||||
cp = lws_json_simple_find(in, in_len, "\"nbf\":", &len);
|
||||
if (!cp || (unsigned long)atol(cp) > now) {
|
||||
lwsl_notice("%s: nbf fail\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ... and not too late for it?
|
||||
*/
|
||||
cp = lws_json_simple_find(in, in_len, "\"exp\":", &len);
|
||||
exp = (unsigned long)atol(cp);
|
||||
if (!cp || (unsigned long)atol(cp) < now) {
|
||||
lwsl_notice("%s: exp fail %lu vs %lu\n", __func__,
|
||||
cp ? (unsigned long)atol(cp) : 0, now);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Caller cares about subject? Then we must have it, and it can't be
|
||||
* empty.
|
||||
*/
|
||||
|
||||
if (sub) {
|
||||
cp = lws_json_simple_find(in, in_len, "\"sub\":", &len);
|
||||
if (!cp || !len) {
|
||||
lwsl_notice("%s: missing subject\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
lws_strnncpy(sub, cp, len, sub_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* If caller has been told a Cross Site Request Forgery (CSRF) nonce,
|
||||
* require this JWT to express the same CSRF... this makes generated
|
||||
* links for dangerous privileged auth'd actions expire with the JWT
|
||||
* that was accessing the site when the links were generated. And it
|
||||
* leaves an attacker not knowing what links to synthesize unless he
|
||||
* can read the token or pages generated with it.
|
||||
*
|
||||
* Using this is very good for security, but it implies you must refresh
|
||||
* generated pages still when the auth token is expiring (and the user
|
||||
* must log in again).
|
||||
*/
|
||||
|
||||
if (csrf_in &&
|
||||
lws_json_simple_strcmp(in, in_len, "\"csrf\":", csrf_in)) {
|
||||
lwsl_notice("%s: csrf mismatch\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (expiry_unix_time)
|
||||
*expiry_unix_time = exp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1468,6 +1468,7 @@ lws_http_cookie_get(struct lws *wsi, const char *name, char *buf,
|
|||
char *p, *bo = buf;
|
||||
|
||||
n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE);
|
||||
lwsl_notice("%s: cookie hdr len %d\n", __func__, n);
|
||||
if (n < bl + 1)
|
||||
return 1;
|
||||
|
||||
|
@ -1475,6 +1476,8 @@ lws_http_cookie_get(struct lws *wsi, const char *name, char *buf,
|
|||
if (!p)
|
||||
return 1;
|
||||
|
||||
lwsl_hexdump_notice(p, n);
|
||||
|
||||
p += bl;
|
||||
n -= bl;
|
||||
while (n-- > bl) {
|
||||
|
@ -1497,3 +1500,117 @@ lws_http_cookie_get(struct lws *wsi, const char *name, char *buf,
|
|||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(LWS_WITH_JOSE)
|
||||
|
||||
#define MAX_JWT_SIZE 1024
|
||||
|
||||
int
|
||||
lws_jwt_get_http_cookie_validate_jwt(struct lws *wsi,
|
||||
struct lws_jwt_sign_set_cookie *i,
|
||||
char *out, size_t *out_len)
|
||||
{
|
||||
char temp[MAX_JWT_SIZE * 2];
|
||||
size_t cml = *out_len;
|
||||
const char *cp;
|
||||
|
||||
/* first use out to hold the encoded JWT */
|
||||
|
||||
if (lws_http_cookie_get(wsi, i->cookie_name, out, out_len)) {
|
||||
lwsl_notice("%s: cookie %s not provided\n", __func__,
|
||||
i->cookie_name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* decode the JWT into temp */
|
||||
|
||||
if (lws_jwt_signed_validate(wsi->context, i->jwk, i->alg, out,
|
||||
*out_len, temp, sizeof(temp), out, &cml)) {
|
||||
lwsl_notice("%s: jwt validation failed\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy out the decoded JWT payload into out, overwriting the
|
||||
* original encoded JWT taken from the cookie (that has long ago been
|
||||
* translated into allocated buffers in the JOSE object)
|
||||
*/
|
||||
|
||||
if (lws_jwt_token_sanity(out, cml, i->iss, i->aud, i->csrf_in,
|
||||
i->sub, sizeof(i->sub),
|
||||
&i->expiry_unix_time)) {
|
||||
lwsl_notice("%s: jwt sanity failed\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* If he's interested in his private JSON part, point him to that in
|
||||
* the args struct (it's pointing to the data in out
|
||||
*/
|
||||
|
||||
cp = lws_json_simple_find(out, cml, "\"ext\":", &i->extra_json_len);
|
||||
if (cp)
|
||||
i->extra_json = cp;
|
||||
|
||||
if (cp)
|
||||
lwsl_hexdump_notice(cp, i->extra_json_len);
|
||||
else
|
||||
lwsl_notice("%s: no ext JWT payload\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
lws_jwt_sign_token_set_http_cookie(struct lws *wsi,
|
||||
const struct lws_jwt_sign_set_cookie *i,
|
||||
uint8_t **p, uint8_t *end)
|
||||
{
|
||||
char plain[MAX_JWT_SIZE + 1], temp[MAX_JWT_SIZE * 2], csrf[17];
|
||||
size_t pl = sizeof(plain);
|
||||
unsigned long long ull;
|
||||
int n;
|
||||
|
||||
/*
|
||||
* Create a 16-char random csrf token with the same lifetime as the JWT
|
||||
*/
|
||||
|
||||
lws_hex_random(wsi->context, csrf, sizeof(csrf));
|
||||
ull = lws_now_secs();
|
||||
if (lws_jwt_sign_compact(wsi->context, i->jwk, i->alg, plain, &pl,
|
||||
temp, sizeof(temp),
|
||||
"{\"iss\":\"%s\",\"aud\":\"%s\","
|
||||
"\"iat\":%llu,\"nbf\":%llu,\"exp\":%llu,"
|
||||
"\"csrf\":\"%s\",\"sub\":\"%s\"%s%s%s}",
|
||||
i->iss, i->aud, ull, ull - 60,
|
||||
ull + i->expiry_unix_time,
|
||||
csrf, i->sub,
|
||||
i->extra_json ? ",\"ext\":{" : "",
|
||||
i->extra_json ? i->extra_json : "",
|
||||
i->extra_json ? "}" : "")) {
|
||||
lwsl_err("%s: failed to create JWT\n", __func__);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* There's no point the browser holding on to a JWT beyond the JWT's
|
||||
* expiry time, so set it to be the same.
|
||||
*/
|
||||
|
||||
n = lws_snprintf(temp, sizeof(temp), "__Host-%s=%s;"
|
||||
"HttpOnly;"
|
||||
"Secure;"
|
||||
"SameSite=strict;"
|
||||
"Path=/;"
|
||||
"Max-Age=%lu",
|
||||
i->cookie_name, plain, i->expiry_unix_time);
|
||||
|
||||
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SET_COOKIE,
|
||||
(uint8_t *)temp, n, p, end)) {
|
||||
lwsl_err("%s: failed to add JWT cookie header\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue