Skip to main content
The login service handles authentication for the customer portal — passwordless email codes, social providers (Google, Facebook), and the token lifecycle that follows. It writes tokens to local storage so subsequent requests stay authenticated.

Setup

import {
  provideContext,
  createLoginService,
  LoginServiceContext,
} from "@juo/blocks";

provideContext(
  root,
  LoginServiceContext,
  createLoginService({
    adapter: myLoginAdapter,
    formatError: (err) => ({ message: String(err), code: undefined }),
    router: routerService,
    shop: { domain: "my-shop.myshopify.com", locale: "en" },
    onLoginSuccess: async () => {
      // hook called after a successful auth
    },
  }),
);
The login service depends on three other dependencies passed at construction time:
  • adapter — connects to the auth backend (passwordless start/verify, social authenticate, refresh token).
  • router — a minimal router with push / replace so the service can redirect post-login.
  • shop{ domain, locale }; locale can be a Signal<string> so login adopts the current theme locale.

Shape

type LoginService = {
  passwordless: {
    email: {
      start(email: string): Promise<Result<void>>;
      verify(email: string, code: string): Promise<Result<void>>;
    };
  };
  social: {
    start(provider: "Google" | "Facebook"): Promise<Result<void>>;
    verify(code: string): Promise<Result<void>>;
  };
  logout(): Promise<Result<void>>;
  getToken(): Promise<string | null>;
  // ...reactive signals for tokens, expires, current user email
};

Example: passwordless start

import { injectContext, LoginServiceContext } from "@juo/blocks";

async function startLogin(el: HTMLElement, email: string) {
  const login = injectContext(el, LoginServiceContext);
  const result = await login.passwordless.email.start(email);
  if (result._tag === "Failure") {
    console.error(result.error.message);
    return;
  }
  // show the code input
}

async function verifyCode(el: HTMLElement, email: string, code: string) {
  const login = injectContext(el, LoginServiceContext);
  const result = await login.passwordless.email.verify(email, code);
  if (result._tag === "Success") {
    // service redirects via the router automatically
  }
}

Token management

getToken() returns the current access token, refreshing it transparently if it has expired (using the stored refresh token). Wire it into the fetch layer to ensure all API requests include a valid access token:
async function authedFetch(url: string, init?: RequestInit) {
  const token = await login.getToken();
  return fetch(url, {
    ...init,
    headers: { ...init?.headers, Authorization: `Bearer ${token}` },
  });
}