Skip to content

Shared sign-in

auth.hasanat.dev is a shared sign-in service for every site served on hasanat.dev domains. A user signs in once with a Google or GitHub account, and every Hasanat site recognises them.

  • After sign-in, the service sets the authjs.session-token cookie on the .hasanat.dev domain. Browsers send it to every site on https://*.hasanat.dev.
  • The cookie is encrypted with a secret that only the auth service holds, so your app never decodes it. Your app asks the service instead: it forwards the incoming cookie header to https://auth.hasanat.dev/api/auth/session and receives the session JSON, or null when nobody is signed in.
  • The cookie is httpOnly, Secure, and SameSite=Lax, so page scripts cannot read it.
  • Sessions expire after 30 days without use, and every session check extends them.
  • Sign-in page: https://auth.hasanat.dev/api/auth/signin?callbackUrl=<url-encoded return URL>
  • Session: https://auth.hasanat.dev/api/auth/session
  • Sign-out: https://auth.hasanat.dev/api/auth/signout
  • User account: https://auth.hasanat.dev/account

The service accepts return URLs on https://hasanat.dev, single-level subdomains such as https://name.hasanat.dev, and http://localhost. Any other return URL sends the user back to auth.hasanat.dev itself. The sign-in page is in English today.

{
"user": {
"id": "…",
"name": "…",
"email": "…",
"image": "…",
"provider": "google",
"githubUsername": null,
"role": "user"
},
"expires": "…"
}
  • id is a stable user identifier. Key your app’s data to it.
  • provider is google or github.
  • role comes from the user’s record in the auth service, defaults to user, and is read at sign-in. It is not a Hasanat project role such as lead or moderator. Keep your app’s own permissions in its database.

The starter is already wired to the service. src/middleware.ts asks the auth service for the session and puts the user in Astro.locals.user, or null.

---
// A page that requires sign-in
import { requireAuth } from "@/lib/auth";
const redirect = requireAuth(Astro.locals.user, Astro.url.href);
if (redirect) return redirect;
const user = Astro.locals.user; // { id, name, email, image, role }
---
// Sign-in and sign-out links, and the admin check
import { getSignInUrl, getSignOutUrl, isAdmin } from "@/lib/auth";
const signIn = getSignInUrl(Astro.url.href);
const signOut = getSignOutUrl();
const admin = isAdmin(Astro.locals.user); // role === "admin"
// A protected API route
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ locals }) => {
if (!locals.user) return new Response("Unauthorized", { status: 401 });
return Response.json({ id: locals.user.id });
};

Pages served on a hasanat.dev subdomain can ask for the session from the browser, because the auth service accepts credentialed requests from hasanat.dev origins:

const res = await fetch("https://auth.hasanat.dev/api/auth/session", {
credentials: "include",
});
const session = await res.json(); // null when signed out

Shared sign-in does not work on localhost: the cookie belongs to hasanat.dev domains, so it is never sent to your machine. In development, the starter points its sign-in links to http://localhost:8788, a local copy of the auth service that most builders do not run, because its repository is private.

So Astro.locals.user stays null locally. Build the public pages first, and try sign-in after deploying to a hasanat.dev subdomain.

There is no OAuth flow for external apps today. auth.hasanat.dev/oauth/authorize and /oauth/token do not exist, and no client IDs or secrets are issued. The commented HASANAT_CLIENT_ID and HASANAT_CLIENT_SECRET lines in the starter’s .env.example are not used.

To use Hasanat accounts in an external app:

  1. Serve it on a hasanat.dev subdomain if you can, and shared sign-in works. See deployment and subdomains.
  2. Otherwise, request “sign-in for an external app” from the platform dashboard, and a maintainer will look at your case with you.

Your app receives the user’s ID, name, email, and image from the auth service. The starter’s data policy sets the rules:

  • Use the ID to associate data in your app, and show the user’s name and image.
  • Do not store the email or image in your database unless the app needs it and you disclose it.
  • Never share user data with third parties, sell it, or use it for advertising.
  • Include a short privacy notice users can reach, even a footer link.
  • Don’t ask for AUTH_SECRET or copy it into your app. Your app does not need it, and it is never sent in any request.
  • Don’t store passwords, tokens, or the session cookie itself.
  • Don’t try to decode the session cookie yourself: ask the auth service.
  • Don’t add another sign-in provider, such as Firebase Auth or Clerk, to starter apps.