Aller au contenu

Interface utilisateur · Guide pratique

Pages d'authentification

Ajoutez les routes de connexion, vérification, réinitialisation et deuxième facteur autour des formulaires fournis.

Le SDK fournit les formulaires, mais votre application possède les routes, la mise en page, les métadonnées et la navigation. L'expérience reste cohérente avec votre produit tandis que Krakstack Auth gère les identifiants et sessions.

Fournisseur requis

Placez KrakstackAuthProvider au-dessus de toutes les pages auth. Avec le proxy recommandé, omettez baseUrl afin que le client navigateur utilise l'origine de l'application.

tsx
import { Outlet, createRootRoute } from "@tanstack/react-router";
import { KrakstackAuthProvider } from "@krak-stack/auth/components";
import { KrakstackProvider } from "@krak-stack/registry/krakstack-provider";
import { getLocale, locales } from "@/paraglide/runtime";

export const Route = createRootRoute({
  component: () => (
    <KrakstackProvider locale={getLocale()} locales={locales}>
      <KrakstackAuthProvider
        projectId={import.meta.env.VITE_KRAKSTACK_AUTH_PROJECT_ID}
      >
        <Outlet />
      </KrakstackAuthProvider>
    </KrakstackProvider>
  ),
});

Le fournisseur charge la marque et les méthodes activées pour le projet. Transmettez la langue Paraglide active via le KrakstackProvider englobant, pas par une prop sur KrakstackAuthProvider. Le fournisseur auth synchronise aussi cette langue avec le compte de l'utilisateur connecté; consultez la synchronisation de la langue pour les limites des pages publiques et la gestion des échecs.

Mise en page auth

Créez une disposition sans chemin _auth pour le cadre commun et la redirection des utilisateurs déjà connectés. Validez et conservez le paramètre redirect.

tsx
import { useAtomSuspense } from "@effect/atom-react";
import { Navigate, Outlet, createFileRoute } from "@tanstack/react-router";
import { authSessionAtom } from "@krak-stack/auth/components";
import { Schema } from "effect";

const AuthSearch = Schema.toStandardSchemaV1(
  Schema.Struct({
    redirect: Schema.optional(Schema.String),
  }).annotate({ identifier: "AuthSearch" }),
);

const safeReturnPath = (value: string | undefined) => {
  if (!value?.startsWith("/") || value.startsWith("//")) return "/";

  const url = new URL(value, "https://app.invalid");
  if (url.origin !== "https://app.invalid") return "/";
  return `${url.pathname}${url.search}${url.hash}`;
};

export const Route = createFileRoute("/_auth")({
  validateSearch: AuthSearch,
  component: AuthLayout,
});

function AuthLayout() {
  const search = Route.useSearch();
  const session = useAtomSuspense(authSessionAtom(), {
    suspendOnWaiting: true,
  }).value;

  if (session) {
    return <Navigate href={safeReturnPath(search.redirect)} reloadDocument />;
  }

  return (
    <main className="mx-auto flex min-h-screen max-w-md items-center px-4">
      <Outlet />
    </main>
  );
}

Le schéma valide la forme de la recherche; safeReturnPath impose la frontière de sécurité. Ne transmettez jamais une URL absolue ou relative au protocole non approuvée à <Navigate href>.

Ajouter les routes

Créez les chemins publics suivants. Les composants utilisent ces noms pour leurs liens et redirections :

RouteComposantFonction
/sign-inSigninMot de passe, code courriel et connexion sociale
/verify-emailVerifyEmailCode de vérification du courriel
/reset-passwordResetPasswordDemande ou finalisation d'une réinitialisation
/2faTwoFactorDéfi TOTP, courriel ou code de secours

Chaque route TanStack suit le même modèle :

tsx
import { Signin } from "@krak-stack/auth/components";
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/_auth/sign-in")({
  component: () => <Signin />,
});

Répétez avec VerifyEmail, ResetPassword et TwoFactor. Il n'existe ni route /sign-up ni composant Signup. Le code à usage unique par courriel et Google créent un compte lorsque l'identité n'existe pas, directement depuis le flux de connexion; l'authentification par mot de passe sert uniquement à se connecter. Conservez les chemins publics exacts sauf si vous personnalisez aussi tous les liens et rappels.

Mot de passe oublié

Signin pointe vers /reset-password. Sans jeton, ResetPassword collecte le courriel et demande un lien de réinitialisation. Lorsque l'utilisateur revient depuis ce courriel avec un jeton, le même composant affiche le formulaire du nouveau mot de passe. La réponse reste générique afin de ne pas révéler l'existence d'un compte.

Routes protégées

Utilisez une route parente pour l'expérience de navigation :

tsx
import { useAtomSuspense } from "@effect/atom-react";
import { Navigate, Outlet, createFileRoute } from "@tanstack/react-router";
import { authSessionAtom } from "@krak-stack/auth/components";

export const Route = createFileRoute("/_protected")({
  component: ProtectedLayout,
});

function ProtectedLayout() {
  const location = Route.useLocation();
  const session = useAtomSuspense(authSessionAtom(), {
    suspendOnWaiting: true,
  }).value;

  if (!session?.user) {
    return <Navigate to="/sign-in" search={{ redirect: location.href }} />;
  }

  return <Outlet />;
}

Cette garde ne remplace pas l'autorisation backend. Chaque opération protégée doit aussi appeler AuthService.requireUser(), requireOrganization() ou requireUserOrganization().

Accès à l'organisation

MemberRequired peut expliquer l'absence d'accès et permettre l'acceptation d'une invitation :

tsx
import { MemberRequired } from "@krak-stack/auth/components";

<MemberRequired
  organizationId={organizationId}
  contactEmail="[email protected]"
>
  <OrganizationDashboard />
</MemberRequired>;

Il s'agit uniquement d'une frontière d'interface. Répétez les vérifications d'adhésion et de rôle dans les services backend.