Setting HTTPOnly Cookies on a Golang Backend and Flutter Front-end - dart

I have spent the entire day looking for an answer to this, and i have not been able.
I am trying to learn Flutter as a front-end framework. in the past i built my webpages with html, css, and vanilla js, but i was able to interpolate dynamic data from the server with php.
I would like to use httpOnly cookies for authenticating user sessions, as i have in the past with php. I'm open to other secure methods, but i am not interested in JWT.
Obviously i cannot access httpOnly cookies from my Flutter App, but i can access them with Golang, which i use for serving the html build, and on my api endpoints.
What i want to do is either, read the httpOnly cookie in golang, and then pass some information into my flutter build to be parsed at runtime, or somehow return an httpOnly cookie from an api endpoint, and persist that to the client that made the call, but i cannot for the life of me figure out how to do either.
I am open to ANY suggestions.
tl;dr on a server with golang backend and flutter front end, how do i read an httpOnly cookie in golang and based on that value, populate values in the flutter front end at runtime, or pass a variable from golang http.HandleFunc that can be read by Flutter at runtime?

you can access a cookie by its name in Go through the request r *http.Request
var myToken string
cookie, err := r.Cookie("myToken")
if err != nil {
myToken = ""
} else {
myToken = cookie.Value
}
you can then do user lookup or anything with that token inside a middleware and pass it to the next handler
ctxWithUser = context.WithValue(r.Context(), "user", authorizedUser)
rWithUser = r.WithContext(ctxWithUser)
next.ServeHTTP(w, rWithUser)
finally in your handler get the request context value
ctxUser := r.Context().Value("user").(user.User)

Related

Is there any way, how to get the redirect uri?

Background:
Let's have a WebAssembly (wasm) originating from .net code.
This wasm uses HttpClient and HttpClientHandler to access a backend API at https://api.uri.
The actual backend API location might change in time (like https://api.uri/version-5), but there is still this fixed endpoint, which provides redirection (3xx response) to the current location (which is in the same domain).
The API allows CORS, meaning it sends e.g. Access-Control-Allow-Origin: * headers in the responses.
In the normal (non-wasm) world, one just:
Plainly GETs the https://api.uri with no additional headers (CORS safe).
Retrieve the Location: header (containing e.g. https://api.uri/version-5) from the 3xx response as the final URI.
GETs/POSTs the final URI with additional headers (as needed, e.g. custom, auth, etc.).
Note: In ideal world, the redirection is handled transparently and the first two steps can just be omitted.
Although in the wasm world:
You are not allowed to (let the wasm/browser) send the OPTIONS pre-flight requests to a redirecting endpoint (https://api.uri).
You can't send any non-cors headers, when wanting to prevent pre-flight requests (reason for two stages, plain and full, described above).
You can't see the Location: header value (like https://api.uri/version-5) when trying the manual redirection (HttpClientHandler.AllowAutoRedirect = false), because the response is just artificially crafted with HTTP status code of 0 and ReasonPhrase == "opaqueredirect" - adoption to browser's Fetch API. What a nonsense! #1...
You can't see the auto-followed Location: header value in response.RequestMessage?.RequestUri, when trying the (default) automatic redirection (HttpClientHandler.AllowAutoRedirect = true), because there is still the original URI (https://api.uri) instead of the very expected auto-followed one (https://api.uri/version-5). What a nonsense! #2...
You can't send the full blown request with all the headers and rely on the automatic redirection, because it would trigger pre-flight, which is sill not allowed on redirecting endpoint.
So, the obvious question is:
Is there ANY way, how to handle such simple scenario from the Web Assembly?
(and not crash on CORS)
GET https://api.uri => 3xx, Location: https://api.uri/version-5
GET https://api.uri/version-5, Authorization: Basic BlaBlaBase64= ; Custom: Cool-Value => 200
Note: All this has been discovered within the Uno Platform wasm head, but I believe it applies for any .net wasm.
Note: I also guess "disabled" CORS (on the request side, via Sec-Fetch-Mode: no-cors) wouldn't help either, as then such request is not allowed to have additional headers/methods, right?

Import Authentication in POST API Call

I'm trying to make a POST API call to an address, including existing cookies, however the request still returns a 403 forbidden error due to authentication requirements.
The request url is as follows.
https://www.imdb.com/list/ls093916945/tt0096697/add
I'm using existing cookies stored within my browser (after manually logging in to IMDb) and copied across for simplicity of testing.
The current python code is as follows
import requests
cookieData = {
'session-id':'146....',
'ubid-main':'132.....',
'adblk':'adblk_yes',
'x-main':'i2qE6E#EK.....',
'at-main':'Atza|IwEB.....',
'sess-at-main':'"ksn3tObY6.....',
'id':'BCYkx.....',
'uu':'BCYvWpz6s5.....',
'sid':'BCYss.....',
'session-id-time':'20827....',
'session-token':'5SB4.....',
'csm-hit':'tb:9A.....1'
}
r = requests.post('https://www.imdb.com/list/ls093916945/tt2724064/add', cookies=cookieData)
print(r)
How do I import the cookie authentication data and authenticate my request?

Angular Universal / Node: Backend not accessing session. Creating new on each reload

I am using session based authentication in my Angular Universal app. Problem is when http request is made from Angular app, backend (node.js) doesn't access the ongoing session, but creates new. You might think this is because cors, but the thing is, the first initial load only doesn't access session. So when I open up my app on page that has resolver or guard, that is making http request. That http request is going to create new session. Then navigating to other pages in app, it all works. http requests made after initial load will access the session. If I start from page that has no resolver/guards and then navigate to page that has and makes http request, this request will access to session.
Here is how my session is setup in index.js:
var sessionStore = new MySQLStore(options);
app.use(
session({
key: 'sessionStorage',
store: sessionStore,
secret: config.get('demoSess'),
saveUninitialized: false,
resave: false,
name: 'demo',
cookie: {
maxAge: 60000,
secure: false
},
})
)
const cors = require('cors');
app.use(cors({
origin: [
'http://localhost:4200'
],
credentials: true
})
);
And this how http request is made from frontend:
this.http.get(environment.apiUrl+'/server/page/auth', {withCredentials: true});
Is this how it should be? Backend runs on port 8080 and frontend 4200.
In app.module.ts, I have written TransferHttpCacheModule. If I remove it, I can see from backend, when I console log something, that first http request is made twice- first one doesn't access session and then second one does. So if I was to console.log(req.session.userId) in /server/page/auth, I would get undefined and 1 on next line. As I read, something like this was normal and to get around it, transferstate comes to into play, but as I understand TransferHttpCacheModule is basically easy way to do the transferstate. I tried also with writting the transferstate into resolver and outcomes was same- one request is only made, but that request wont access session.
I am hoping I am missing something when I am making http request from frontend or my session/cors is missing something. At this point I am running out of idea what to check or test, any hint what to check out is welcoming.
So I started to build around my authentication in Angular to use localStorage. I ran there into problem and while searching for solution I ran into tutorial talking something about isPlatformBrowser. So I started thinking, maybe Angular Universal in some way is making two request, but these two request are different and I need to eliminate one of them. So I ended up wrapping my http request with if(isPlatformBrowser(this.platformId)) { } and so far it seems I got my problem fixed.

Cookies don't work over WebSocket on Apple devices

Iv'e been setting and retrieving cookies over pre-WebSocket stage to identify a user. I assumed everything would work as over a typical HTTP exchange.
This has worked flawlessly on all browsers I've tested them on, but reports started coming in that on iPhones the sign-ins would not be retained at all, signifying that cookies either were not set or sent back to the server.
// fret not, safety checks removed for brevity
const (
sessionKeyCookieName string = "session-key"
webSocketPath string = "/ws"
)
func serveWs(w http.ResponseWriter, r *http.Request) {
var sessionKey [sha1.Size]byte
var u *user
for _, cookie := range r.Cookies() {
if cookie.Name != sessionKeyCookieName {
continue
}
slice, err := base64.StdEncoding.DecodeString(cookie.Value)
if err != nil {
continue
} else {
copy(sessionKey[:], slice)
}
}
u, _ = getUserBySessionKey(sessionKey)
// regenerate key. TODO: does that add security?
rand.Read(sessionKey[:])
header := make(http.Header)
header.Add("Set-Cookie", (&http.Cookie{
Name: sessionKeyCookieName,
Value: base64.StdEncoding.EncodeToString(sessionKey[:]),
MaxAge: int(sessionLength.Seconds()),
HttpOnly: true,
Domain: strings.SplitN(r.Host, ":", 2)[0],
}).String())
ws, err := upgrader.Upgrade(w, r, header)
if err != nil {
if _, ok := err.(websocket.HandshakeError); !ok {
log.Println(err)
}
return
}
// do things to `user` so their messages go to where they're needed
go c.writePump()
c.readPump()
}
Headers as seen on Firefox network dev tool
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: eSazcZyZKj2dfa2UWSY+a4wThC8=
Access-Control-Allow-Origin: *
Set-Cookie: session-key=RNStK2z2gAsan7DyNKQ+efjyr7c=; Domain=redacted.org; Max-Age=259200; HttpOnly
Am I skipping some step that would allow Safari to store cookies, or is this an issue upstream1?
P.S. I'd really like to retain this approach, since I can use HTTP-only cookies and that mostly ensures that JavaScript has no access to them.
Looks like Gary is having similar issues as well. In short, cookies don't travel back over WebSockets.
TLDR: it's the HttpOnly flag.
It appears that while some browsers do allow Set-Cookie header in a response for WebSocket connection to have HttpOnly flag, iOS Safari considers the situation as "non-HTTP" and blocks this.
Interestingly, while setting HttpOnly cookies is not possible, HttpOnly cookies are sent in request headers while connecting a WebSocket. This leaves a pair of options:
Increase risk and omit HttpOnly;
Set your cookies with another plain HTTP request, quite possibly one that doesn't even have a response body.
I'd consider iOS Safari's behavior to be incorrect compared to what's outlined in RFC 6265 Storage model
If your Set-Cookie header works in other browsers my guess is it's an upstream issue, specifically iOS Safari has the ability to block cookies. By default iOS Safari blocks 3rd party cookies.
Can a webpage in mobile Safari check whether Settings > Safari > Accept Cookies 'From visited' or 'Always' is selected?
If cookies are blocked you can't use them. If you need cookies, detect support by setting a cookie on the login page like enabled=1 and then check for it in /ws handler. If it comes up blank and cookies are blocked you can try redirecting to /please-enable-cookies to ask the user to enable cookies for your site.
Another option is to store signed session data in local storage and include it in each request in the Authorization header. https://jwt.io/

Retrieving the url anchor in a werkzeug request

I have a DAV protocol that stores out-of-band data in the url anchor, e.g. the ghi in DELETE /abc.def#ghi. The server is a Flask application.
I can see the request come in on the wire via tcpdump, but when I look at the werkzeug Request object (such as url() or base_url()), all I get back is /abc.def. The #ghi has been stripped out.
Is there a method that returns this information, or do I have to subclass Request to handle this myself? If so, is there an example I can use as an inspiration?
I ran into the same problem. Facebook authentication API returns the access token behind a hash appended into the redirection url. In the same way, Flask's request.url drops everything in the URL behind the hash sign.
I'm also using Flask so I think you can use my brute-force workaround using Javascript's window.location.href to get the full URL. Then, I just extracted the piece that I needed (the access token), put it into a redirection URL where I can pass the access token as an argument to the receiving view function. Here's the code:
#app.route('/app_response/<response>', methods=['GET'])
def app_response_code(response):
return ''' <script type="text/javascript">
var token = window.location.href.split("access_token=")[1];
window.location = "/app_response_token/" + token;
</script> '''
#app.route('/app_response_token/<token>/', methods=['GET'])
def app_response_token(token):
return token
In case you manage(d) to do this within Werkzeug, I'm interested to know how.
From Wikipedia (Fragment Identifier) (don't have the time to find it in the RFC):
The fragment identifier functions differently than the rest of the URI: namely, its processing is exclusively client-side with no participation from the server
So Flask - or any other framework - doesn't have access to #ghi.
You can do this using flask.url_for with the _anchor keyword argument:
url_for('abc.def', _anchor='ghi')

Resources