I am having problems with cookie based token auth in my chat app. I am using a Go backend with the standard net library to add tokens to response cookies. When a user passes the password validation (by POSTing to the /login path on the auth server), the response cookies should contain an access token for generating API tokens and a refresh token for regenerating the access token.
Here is a markup file containing the structure of the apps services in my dev environment. Each server is being run on sequential ports using Go net/http on localhost (irrelevant services are not shown).
auth_server (
dependencies []
url (scheme "http" domain "localhost" port "8081")
listenAddress ":8081"
endpoints (
/jwtkeypub (
methods [GET]
)
/register (
methods [POST]
)
/logout (
methods [POST]
)
/login (
methods [POST]
)
/apitokens (
methods [GET]
)
/accesstokens (
methods [GET]
)
)
jwtInfo (
issuerName "auth_server"
audienceName "auth_server"
)
)
message_server (
dependencies [auth_server]
url (scheme "http" domain "localhost" port "8083")
listenAddress ":8083"
endpoints (
/ws (
methods [GET]
)
)
jwtInfo (
audienceName "message_server"
)
)
static (
dependencies [auth_server, message_server]
url (scheme "http" domain "localhost" port "8080")
listenAddress ":8080"
)
This is the code that sets the cookies on login. This happens after the password check
// Set a new refresh token
refreshToken := s.jwtIssuer.StringifyJwt(
s.jwtIssuer.MintToken(userId, s.jwtIssuer.Name, RefreshTokenTTL),
)
kit.SetHttpOnlyCookie(w, "refreshToken", refreshToken, int(RefreshTokenTTL.Seconds()))
// set a new access token
accessToken := s.jwtIssuer.StringifyJwt(
s.jwtIssuer.MintToken(userId, s.jwtAudience.Name, AccessTokenTTL),
)
kit.SetHttpOnlyCookie(w, "accessToken", accessToken, int(AccessTokenTTL.Seconds()))
}
func SetHttpOnlyCookie(w http.ResponseWriter, name, value string, maxAge int) {
http.SetCookie(w, &http.Cookie{
Name: name,
Value: value,
HttpOnly: true,
MaxAge: maxAge,
})
}
And here is how I am accessing the cookie when the user requests a API token. The handler calls the GetTokenFromCookie() function and responds with a 401 if an error is returned. The error is this case is "http: named cookie not present"
func GetHttpCookie(r *http.Request, name string) (*http.Cookie, error) {
return r.Cookie(name)
}
func GetTokenFromCookie(r *http.Request, name string) (jwt.Jwt, error) {
tokenCookie, err := GetHttpCookie(r, name)
if err != nil {
// DEBUG
log.Println(err)
return jwt.Jwt{}, err
}
return jwt.FromString(tokenCookie.Value)
}
After a 200 response from the login endpoint, the page redirects to the main app page. On this page, a request is made to the auth server to receive an API token for connecting the live chat message server. As you can see from the log output on the auth server, the access token cookie was not received in the request, so the request returns a 401 code.
2023/05/19 02:33:57 GET [/jwtkeypub] - 200
2023/05/19 02:33:57 GET [/jwtkeypub] - 200
2023/05/19 02:34:23 POST [/login] - 200
2023/05/19 02:34:23 http: named cookie not present
{{ } { } []} http: named cookie not present
2023/05/19 02:34:23 GET [/apitokens?aud=MSGSERVICE] - 401
I believe the problem lies in that I am using localhost and the browser does not transfer the cookie from locahost:8080 to localhost:8081. I was planning on implmenting some sort of mock auth that circumvents reading the cookies for the dev environment to get around this, but I am not sure if this is actually the cause of my problem. Just want to get a second look and see if I can get it working without needing to do that.
UPDATE: I have looked into the network tabs in dev tools: The images show that the response after logging in returns the cookies, but they are not subsequently sent to the auth server on port 8081. I have also looked in cookie storage after getting the 200 response from logging in, there is no cookie present even after receiving them in the response. I am using Firefox on private mode to access the site. Note that the cookie does not include MaxAge even though I set MaxAge in the Go code, this seems like a problem.
UPDATE: Here is the HAR file after logging in. You can see that the response has Max-Age, but it doesn't show up in the cookies tab afterwards.
{
"log": {
"version": "1.2",
"creator": {
"name": "Firefox",
"version": "113.0.1"
},
"browser": {
"name": "Firefox",
"version": "113.0.1"
},
"pages": [
{
"startedDateTime": "2023-05-19T12:16:37.081-04:00",
"id": "page_1",
"title": "Login Page",
"pageTimings": {
"onContentLoad": -8105,
"onLoad": -8077
}
}
],
"entries": [
{
"pageref": "page_1",
"startedDateTime": "2023-05-19T12:16:37.081-04:00",
"request": {
"bodySize": 31,
"method": "POST",
"url": "http://0.0.0.0:8081/login",
"httpVersion": "HTTP/1.1",
"headers": [
{
"name": "Host",
"value": "0.0.0.0:8081"
},
{
"name": "User-Agent",
"value": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0"
},
{
"name": "Accept",
"value": "*/*"
},
{
"name": "Accept-Language",
"value": "en-US,en;q=0.5"
},
{
"name": "Accept-Encoding",
"value": "gzip, deflate"
},
{
"name": "Referer",
"value": "http://localhost:8080/"
},
{
"name": "Content-Type",
"value": "text/plain;charset=UTF-8"
},
{
"name": "Content-Length",
"value": "31"
},
{
"name": "Origin",
"value": "http://localhost:8080"
},
{
"name": "DNT",
"value": "1"
},
{
"name": "Connection",
"value": "keep-alive"
}
],
"cookies": [],
"queryString": [],
"headersSize": 370,
"postData": {
"mimeType": "text/plain;charset=UTF-8",
"params": [],
"text": "{\"username\":\"a\",\"password\":\"a\"}"
}
},
"response": {
"status": 200,
"statusText": "OK",
"httpVersion": "HTTP/1.1",
"headers": [
{
"name": "Access-Control-Allow-Origin",
"value": "*"
},
{
"name": "Set-Cookie",
"value": "refreshToken=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NTExNzc5NyIsImp0aSI6IjIwMUQzODZDNTRBQzlEOUMwRjdCODFBMDVDNDlFQTE1In0.SbxFgEAtZbh0zS-SXZmrVW9iLk-cFz6HcDMU0FHNl-K9BwCeb_boc5igEgImMSYK-NBVQZh1km7YknE-jkBWyF0rIYjSnTzjNUHHwMnn0jE1N-dtEfNRnF1OT0R2bxPSz8gmhtJ3B839xa-jh9uMPMkXEB8BYtABgPH1FqBdijHPUtRVKq6C3ulVleurp2eyF8EHpGLc9rr5wBYSFBk0HQ3FNjjUxfRQLDnzl2xYovoQ2em4grExnkdACxCSpXNtF5bQ7lCnEZyf7-CehrRNwZCpteGKj5ux_wrX_nxma3OEWwrlatML_j-e420TM1tub0C9Ymyt0bMugHw8vaiOGA; Max-Age=604800; HttpOnly"
},
{
"name": "Set-Cookie",
"value": "accessToken=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NDUxNDE5NyIsImp0aSI6IjY2NjU1QjAyNTc4NkRBRTE1M0VDNDI3MzBGMjMxQ0FGIn0.cIs6KGjRGTHaWX_uFTts_V2a3YcBb7LA0jNOBTZeyDmpPQgRlcABnuYkWUIdjUdR6VYnDitFRV-XK2ZSq6Pk_ZgyfvJ3yRzvWGYjXMu7Nq7MLpVvUh9mLKSbKvlqunW6YVamHSCAbYS8-D_pY9fpWxIcXw0qbwA2XfTdzr0Mrw7ntrkdyK7O1QqWamnEHCmpLfJ2XJlQsU0KaD8FjkL76pO3lWmrca3VYnTmjP1Oo1HEhbK3nImtrNeL2khAyb8ns8ROj2HX41IDNK1aHWPfn9J04pgH3AfBfcwhhqZkrKjTVFQAkSYzuvjKPWOfpgYmBMw3Y5nG_PDf-zlvVPrdpQ; Max-Age=1200; HttpOnly"
},
{
"name": "Date",
"value": "Fri, 19 May 2023 16:16:37 GMT"
},
{
"name": "Content-Length",
"value": "0"
}
],
"cookies": [
{
"name": "refreshToken",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NTExNzc5NyIsImp0aSI6IjIwMUQzODZDNTRBQzlEOUMwRjdCODFBMDVDNDlFQTE1In0.SbxFgEAtZbh0zS-SXZmrVW9iLk-cFz6HcDMU0FHNl-K9BwCeb_boc5igEgImMSYK-NBVQZh1km7YknE-jkBWyF0rIYjSnTzjNUHHwMnn0jE1N-dtEfNRnF1OT0R2bxPSz8gmhtJ3B839xa-jh9uMPMkXEB8BYtABgPH1FqBdijHPUtRVKq6C3ulVleurp2eyF8EHpGLc9rr5wBYSFBk0HQ3FNjjUxfRQLDnzl2xYovoQ2em4grExnkdACxCSpXNtF5bQ7lCnEZyf7-CehrRNwZCpteGKj5ux_wrX_nxma3OEWwrlatML_j-e420TM1tub0C9Ymyt0bMugHw8vaiOGA"
},
{
"name": "accessToken",
"value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJQMmY2RHg1RWxlYTF5THBUaVpEejBaS3Z1dk1FUkFPZEtBVGkwNDZSc2JNPSIsImF1ZCI6InN0ZWVsaXgiLCJpc3MiOiJzdGVlbGl4IiwiZXhwIjoiMTY4NDUxNDE5NyIsImp0aSI6IjY2NjU1QjAyNTc4NkRBRTE1M0VDNDI3MzBGMjMxQ0FGIn0.cIs6KGjRGTHaWX_uFTts_V2a3YcBb7LA0jNOBTZeyDmpPQgRlcABnuYkWUIdjUdR6VYnDitFRV-XK2ZSq6Pk_ZgyfvJ3yRzvWGYjXMu7Nq7MLpVvUh9mLKSbKvlqunW6YVamHSCAbYS8-D_pY9fpWxIcXw0qbwA2XfTdzr0Mrw7ntrkdyK7O1QqWamnEHCmpLfJ2XJlQsU0KaD8FjkL76pO3lWmrca3VYnTmjP1Oo1HEhbK3nImtrNeL2khAyb8ns8ROj2HX41IDNK1aHWPfn9J04pgH3AfBfcwhhqZkrKjTVFQAkSYzuvjKPWOfpgYmBMw3Y5nG_PDf-zlvVPrdpQ"
}
],
"content": {
"mimeType": "text/plain",
"size": 0,
"text": ""
},
"redirectURL": "",
"headersSize": 1347,
"bodySize": 1748
},
"cache": {},
"timings": {
"blocked": 0,
"dns": 0,
"connect": 0,
"ssl": 0,
"send": 0,
"wait": 13,
"receive": 0
},
"time": 13,
"_securityState": "insecure",
"serverIPAddress": "0.0.0.0",
"connection": "8081"
}
]
}
}
The response appears to have the cookies, but they don't get saved.
And the next request to the auth server doesn't have any cookies added.