Closed tocttou closed 7 years ago
Seems like a perfect fit to rewrite this in the reverse proxy layer. nginx, apache etc. are all able to read and write HTTP headers while proxying a request.
But, how are Cookies less vulnerable to XSS than localstorage? Edit: I see in your link, HttpOnly. Cookie technology must have moved on from what I knew back in the days...
Using a reverse proxy is a fair point, thanks. But it would be better if this could be integrated in the main project as it is a valid use case and easy to implement (probably).
@tocttou have you investigated doing the proxy thing? If so I'd love an nginx snippet to include in the docs.
Hello
Yes I was able to get it working using lua-nginx-module to rewrite the request.
If your Postgrest API server is on http://localhost:3000
and your Nginx Proxy is on http://localhost:3001
, you can use this nginx proxy config and make request to your nginx proxy with a cookie access_token
that contains the jwt (it rewrites the headers to include a Authorization: Bearer <jwt>
header:
server {
listen 0.0.0.0:3001;
location / {
rewrite_by_lua_block {
local cookie_value = ngx.req.get_headers()["Cookie"]
if cookie_value ~= nil then
local jwt = cookie_value:match("access_token=([^ ]+)")
ngx.req.set_header("Authorization", "Bearer " .. jwt)
end
ngx.req.clear_header("Cookie")
}
proxy_pass http://0.0.0.0:3000;
}
}
Actual request (to nginx proxy by the client):
GET HTTP/1.1
Host: localhost:3001
Cookie: access_token=mah.osum.token
Request relayed to Postgrest:
GET HTTP/1.1
Host: localhost:3000
Authorization: Bearer mah.osum.token
Note that the regex used to extract the access_token
only works correctly when there is a single cookie.
Closing this since implementation in OpenResty is the preferred way. The only way to add is there is a better way to read the cookie like this
local ck = require 'resty.cookie'
local cookie = ck:new()
local token = cookie:get('COOKIE_NAME_HERE')
What I did is setting local config in login plpgsql function, and do the transfer job in nginx (without lua).
_cookie := format('[{"set-cookie": "access_token=%s; path=/; HttpOnly; max-age=86400"}]', _token);
PERFORM set_config('response.headers', _cookie, true);
expected postgrest would support to read token from cookie, but currently seems not. So I have to transfer token from cookie header to authorizaition header.
if ($cookie_access_token) {
set $auth "Bearer $cookie_access_token";
}
if ($http_authorization) {
set $auth $http_authorization;
}
proxy_set_header Authorization $auth;
I hope it could support to read access_token or session_token in cookie header as jwt token.
expected postgrest would support to read token from cookie, but currently seems not
I would appreciate this option too
for anyone who using envoy proxy.
- name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
-- called on request path
function envoy_on_request(request_handle)
local headers = request_handle:headers()
local cookieString = headers:get("cookie")
if cookieString ~= nil then
local jwt = cookieString:match("jwt=([^; ]+)")
headers:add("authorization", "Bearer " .. jwt)
end
end
For anyone using Caddy 2.4.5, this Caddyfile can be a helpful starting point:
{
debug
}
:8080
reverse_proxy /rpc/login 127.0.0.1:3000 {
# Include the AuthToken cookie in future requests, in case the database needs
# to perform auditing, automatic logout etc. If the AuthToken cookie has no
# value, then PostgREST will default to the anonymous role.
header_up Authorization "Bearer {http.request.cookie.AuthToken}"
# The /rpc/login endpoint will create a temporary response header called
# App-Auth-Token, that will contain the JWT bearer authorization token. We
# will use that header value to set the AuthToken cookie.
# todo: For HTTPS, use Set-Cookie "__Host-Token=3h93…;Path=/;Secure;HttpOnly;SameSite=Strict"
@match_status_ok status 2xx
handle_response @match_status_ok {
header Content-Type application/json
header Set-Cookie "AuthToken={http.reverse_proxy.header.App-Auth-Token};HttpOnly;Path=/;SameSite=Strict"
respond `{
"auth_role" : "{http.reverse_proxy.header.App-Auth-Role}",
"auth_session_id" : "{http.reverse_proxy.header.App-Auth-Session}",
"auth_user_id" : "{http.reverse_proxy.header.App-Auth-User}",
}` {http.reverse_proxy.status_code}
}
# In case login fails, let's clear the AuthToken cookie. Login would fail if
# there is an incorrect username/password, or if the existing authorization
# token somehow caused validation to fail.
# Uncomment these lines if you want to clear the cookie if login fails.
# @match_status_errors status 4xx
# handle_response @match_status_errors {
# header Content-Type application/json
# header Set-Cookie "AuthToken=;HttpOnly;Path=/;SameSite=Strict"
# respond `{ "message" : "{http.reverse_proxy.status_text}" }` {http.reverse_proxy.status_code}
# }
# Now that we are done with these temporary headers, we can remove them.
header_down -App-Auth-Token
header_down -App-Auth-Session
header_down -App-Auth-User
header_down -App-Auth-Role
}
reverse_proxy /rpc/logout 127.0.0.1:3000 {
header_up Authorization "Bearer {http.request.cookie.AuthToken}"
header_down Set-Cookie "AuthToken=;HttpOnly;Path=/;SameSite=Strict"
}
reverse_proxy 127.0.0.1:3000 {
header_up Authorization "Bearer {http.request.cookie.AuthToken}"
}
Anybody implemented this in ingress nginx on kubernetes?
Solved in kubernetes nginx ingress by doing this, inspired in @TonnyLTP solution:
This solution includes an access token and a refresh token for more secure login implementations.
nginx.ingress.kubernetes.io/configuration-snippet: |
if ($cookie_access_token) {
set $auth "Bearer $cookie_access_token";
}
if ($http_authorization) {
set $auth $http_authorization;
}
if ($cookie_refresh_token) {
set $auth "Bearer $cookie_refresh_token";
}
proxy_set_header Authorization "${auth}";
The ability to set custom headers was added in PR 986, which I found in this discussion.
@TonnyLTP and @yevon have the closest approach for my use case--as of now I am pretty confident I can change the /login endpoint to respond with a set-cookie header to store the JWT, but once I've done that how do I ensure the rest of the endpoints in postgrest are checking the cookie header for a JWT?
Postgrest always validates if there is an auth header present for all endpoints, you have to explicity specify which ones do not require any auth, like the login. Once you specify your token http only cookie is valid for your api.example.com, the web browser will always send that cookie to the server automatically for every request against api.example.com. And with the nginx / proxy approach, you transform the cookie to an auth header before passing it to the postgrest backend.
Thank you @yevon; so there isn't a way to configure pgRest to check the cookie directly instead of the Auth header? I was hoping I could simply change a config setting instead of running a reverse proxy--if transforming is required then I'll be running another server for that purpose alone.
I'm a novice end user, and I really appreciate the help; my goal is to use the JWT auth/auth as described in SQL User Management and Tutorial 1, with the sole difference of storing the JWT as an Http Only
SameSite
cookie.
You could achieve this by implementing a pre-validation pgsql script and validate the token yourself, but this would be even more complicated than the proxy approach. I think that right now there is no way to accept tokens via cookie officially, or compatibility with a short term token with long term refresh tokens. It would be nice having this kind of cookie based support.
I could not find a way in documentation to use JWT authentication using cookies instead of the Bearer scheme in headers which requires you to store the JWT in localStorage (which can be a fatal in case of an XSS vulnerability). For reference: Where to store JWTs.
This would require recognising the token as: