Merge pull request #21363 from edx/robrap/adr-update-jwt-login-redirect
Update ADR around HTTP_USE_JWT_COOKIE
This commit is contained in:
@@ -61,8 +61,8 @@ Login -> Cookie -> API
|
||||
* Contains only the public key signature portion of the JWT.
|
||||
* Enable HTTPOnly_ so the signature is unavailable to JS code. See `JWT Cookie Security`_ below.
|
||||
|
||||
#. **Automatically recombine and extract the JWT from Cookies on API calls.**
|
||||
* We will create a new middleware that will reconstitute the divided JWT from its two cookies and store the
|
||||
#. **Automatically recombine and extract the JWT from Cookies on API calls.**
|
||||
* A new middleware JwtAuthCookieMiddleware will reconstitute the divided JWT from its two cookies and store the
|
||||
recombined JWT in a temporary cookie specified by JWT_AUTH_COOKIE_.
|
||||
* The `Django Rest Framework JWT`_ library we use makes use of the JWT_AUTH_COOKIE_ configuration setting.
|
||||
When set, the JSONWebTokenAuthentication_ class `automatically extracts the JWT from the cookie`_. Since all
|
||||
@@ -81,12 +81,19 @@ Login -> Cookie -> API
|
||||
* To prevent this issue, we will introduce a new HTTP header called "HTTP_USE_JWT_COOKIE" that will be selectively
|
||||
set only by microfrontends that want to use JWT cookie based authentication. The new middleware will check for
|
||||
this header before trying to reconstitute and use the JWT token.
|
||||
* Additionally, select login-required APIs can be updated to redirect the caller to the Login page when the JWT
|
||||
expires. This can be accomplished by enabling `JwtRedirectToLoginIfUnauthenticatedMiddleware`_ in the Django
|
||||
service and updating the API to require the `LoginRedirectIfUnauthenticated`_ permission class. The middleware
|
||||
automatically sets "HTTP_USE_JWT_COOKIE" for incoming requests to APIs that require the
|
||||
`LoginRedirectIfUnauthenticated`_ permission.
|
||||
|
||||
.. _`Lightrail's design`: https://medium.com/lightrail/getting-token-authentication-right-in-a-stateless-single-page-application-57d0c6474e3
|
||||
.. _Django Rest Framework JWT: https://getblimp.github.io/django-rest-framework-jwt/
|
||||
.. _JWT_AUTH_COOKIE: https://github.com/GetBlimp/django-rest-framework-jwt/blob/master/docs/index.md#jwt_auth_cookie
|
||||
.. _JSONWebTokenAuthentication: https://github.com/GetBlimp/django-rest-framework-jwt/blob/0a0bd402ec21fd6b9a5f715d114411836fbb2923/rest_framework_jwt/authentication.py#L71
|
||||
.. _automatically extracts the JWT from the cookie: https://github.com/GetBlimp/django-rest-framework-jwt/blob/0a0bd402ec21fd6b9a5f715d114411836fbb2923/rest_framework_jwt/authentication.py#L86-L87
|
||||
.. _JwtRedirectToLoginIfUnauthenticatedMiddleware: https://github.com/edx/edx-drf-extensions/blob/0351010f1836e4cebd6bdc757d477b2f56265b17/edx_rest_framework_extensions/auth/jwt/middleware.py#L76
|
||||
.. _LoginRedirectIfUnauthenticated: https://github.com/edx/edx-drf-extensions/blob/0351010f1836e4cebd6bdc757d477b2f56265b17/edx_rest_framework_extensions/permissions.py#L147
|
||||
|
||||
|
||||
JWT Cookie Lifetime
|
||||
@@ -106,7 +113,7 @@ JWT Cookie Lifetime
|
||||
#. **Revocation with short-lived JWTs** Given the tradeoff between long-lived JWTs versus immediacy of revocation, we
|
||||
need to configure an appropriate expiration value for JWT cookies. In a future world with an API gateway, we *may*
|
||||
have longer lived JWTs with a *stateful* check against a centralized `JWT blacklist`_ and each JWT uniquely
|
||||
identified by a `JWT ID (jti)`_. In the meantime, we will err on the side of security and have short-lived JWTs.
|
||||
identified by a `JWT ID (jti)`_. In the meantime, we will err on the side of security and have short-lived JWTs.
|
||||
|
||||
#. **Refresh JWT Cookies.** When a JWT expires, we do not want to ask the user to login again while their browser
|
||||
session remains alive. A microfrontend will detect JWT expiration upon receiving a 401 response from an API
|
||||
@@ -129,7 +136,7 @@ JWT Cookie Content
|
||||
supported by a browser. `Modern browsers have treated this requirement as a maximum`_ - and hence do not support
|
||||
more than 4096 bytes. Our current JWT size is about 970 bytes (varying with size of user identifiers, like user's
|
||||
name, etc). (Side note: Signing a JWT with a 2048 byte asymmetric key increases the JWT's size by 325 bytes.)
|
||||
|
||||
|
||||
To minimize the JWT's size from the start, we should eliminate any unnecessary data that is `currently embedded
|
||||
in the JWT`_. For example:
|
||||
|
||||
@@ -149,12 +156,12 @@ JWT Cookie Security
|
||||
|
||||
#. **Enable CSRF Protection.** Storing JWTs in HTTP cookies is potentially vulnerable to CSRF attacks.
|
||||
See `JWT Cookie Storage Security`_. To protect against this:
|
||||
|
||||
|
||||
* Enable the HttpOnly_ flag on the **"JWT Signature Cookie"**, so Javascript code cannot misuse the JWT.
|
||||
* Enable the Secure_ flag on the cookie, so it will not be sent (and thus leaked) through an unencrypted channel.
|
||||
* Enable `Django's CSRF middleware`_ for every response.
|
||||
* Ensure all GET requests are side-effect free.
|
||||
|
||||
|
||||
* Note: The `same-origin policy`_ protects against CSRF attacks on GET requests since the rogue website cannot
|
||||
access the response from the GET request.
|
||||
* However, even though the rogue website cannot access the response, the GET request is still processed on the
|
||||
@@ -181,7 +188,7 @@ JWT Cookie Security
|
||||
Consequences
|
||||
------------
|
||||
|
||||
#. Since session cookies have a limited size of `at least 4096 bytes`_, we will need to monitor its size increase
|
||||
#. Since HTTP cookies have a limited size of `at least 4096 bytes`_, we will need to monitor its size increase
|
||||
over time and implement a warning before it exceeds the size. Having this hard limit requires us to be judicious
|
||||
of what data is included in the JWT. A bloated JWT is not necessarily a benefit to overall web performance.
|
||||
|
||||
@@ -199,6 +206,7 @@ Consequences
|
||||
#. Continue to use and expand the current `JS-accessible user-info cookie`_, which contains user-data.
|
||||
#. Have the server populate the initial DOM with this data, but this would only work for server-generated HTML.
|
||||
|
||||
.. _at least 4096 bytes: http://browsercookielimits.squawky.net/
|
||||
.. _JWT sessionStorage and localStorage Security: https://stormpath. com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage#so-whats-the-difference
|
||||
.. _JS-accessible user-info cookie: https://github.com/edx/edx-platform/blob/70d1ca474012b89e4c7184d25499eb87b3135409/common/djangoapps/student/cookies.py#L151
|
||||
|
||||
|
||||
Reference in New Issue
Block a user