allow moving the authentication flow to a subdomain
slymax opened this issue · comments
I'm not sure if this is supposed to work already, but I would like to have my authentication flow on auth.example.com
and the main site on example.com
. One issue I'm experiencing with this is that if the cookie gets set on the subdomain (by handleCallback
), it gets blocked on the main domain, presumably because the Domain
attribute is not set. Maybe making the domain attribute customizable would allow having the authentication flow on a subdomain?
https://web.dev/first-party-cookie-recipes/#first-party-cookie-recipe-for-sites-with-subdomains
https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent
I think the prefix (__Secure
instead of __Host
) would have to be customizable for this as well. Maybe this would be a good reason to implement #20?
So I've been tinkering a bit with this, and it looks like these steps are necessary to make this work:
handleCallback
must add theDomain
attribute and use the__Secure
prefix instead of__Host
when setting the cookie.signIn
,signOut
, andgetSessionId
must be able to handle requests when a__Secure
prefix is present instead of__Host
.- All responses sent to the client must have CORS enabled.
Here's a drop-in solution I've hacked together that works for my use case:
import { signIn, signOut, getSessionId, handleCallback } from "https://slymax.com/scripts/auth.js";
My proposal would be to introduce a handleCallbackWithSubdomain
module that does [1], the functions in [2] become prefix-agnostic, and [3] is left to the developer before sending the response to the client.
Just curious, what is the use case for this? Sounds to me like the sub domain would be acting as a provider in the context of your main app
the use case would be apps that follow the jamstack approach where the content served on example.com
is static and possibly coming from another source (GitHub/Cloudflare Pages, Amazon S3, etc)
So what would your auth flow look like in this case?
on example.com
, there would be a link to auth.example.com/signin
(or auth.example.com/signout
) with a success_url (i.e. redirect) of example.com
. From there, you could then fetch user data, access tokens, etc (e.g. from auth.example.com/user
). This only works, however, if the cookie (set by handleCallback
) is valid for auth.example.com
and example.com
.
@slymax, are you able to share the codebase for your use case?
3. All responses sent to the client must have CORS enabled.
I don't believe CORS is required. Changing the cookie name, adding the domain field and removing the path field should be sufficient.
This only works, however, if the cookie (set by
handleCallback
) is valid forauth.example.com
andexample.com
.
In its current state, the cookie works if all auth happens in auth.example.com
(within the same host). Excuse my ignorance, but could you please elaborate on why the cookie is also needed on example.com
? Do you have an example? The use case makes sense, I just need to be able to justify the new requirement for this module.
sure! I was thinking of doing something like this:
index.html
served from a static file server on example.com
:
<body>
<a href="https://auth.example.com/login?success_url=https://example.com">Login</a><br>
GitHub Access Token: <span id="access-token"></span>
<script>
const response = await fetch("https://auth.example.com/token", {
credentials: "include"
});
const { token } = await response.json();
document.getElementById("access-token").innerText = token;
</script>
</body>
the server on auth.example.com
:
import * as auth from "https://deno.land/x/deno_kv_oauth/mod.ts";
const config = auth.createGitHubOAuthConfig();
const routes = {
login: async request => await auth.signIn(request, config),
logout: async request => await auth.signOut(request, config),
callback: async request => (await auth.handleCallback(request, config)).response,
token: async request => {
const sessionId = await auth.getSessionId(request);
const token = await auth.getSessionAccessToken(config, sessionId);
const response = Response.json({ token });
const origin = request.headers.get("Origin");
response.headers.set("Access-Control-Allow-Credentials", "true");
response.headers.set("Access-Control-Allow-Origin", origin);
return response;
}
};
Deno.serve(request => {
const { pathname } = new URL(request.url);
return await routes[pathname.split("/")[1]](request);
});
I don't believe CORS is required. Changing the cookie name, adding the domain field and removing the path field should be sufficient.
yeah I think only the fetch requests from the client to the subdomain require CORS.
I've also been playing with this a little bit and it looks like this would also concern the cookie set by sign_in
and, as mentioned above, signIn
, signOut
, and getSessionId
would need to be able to handle cookies with a __Secure
prefix, which isn't possible with the way cookie names are currently retrieved. Unfortunately, it's not as easy as adding an options interface to handleCallback
.
I've created #245 which enables defining cookie options. Please give it a test and let me know how you go.
yes, this works well. The removal of getSessionAccessToken
in v0.8 makes use cases like this a bit more complicated but session cookies can now be set to be valid across subdomains 👍