npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fuju-auth-react

v0.1.2

Published

Unified authentication front-end for the Fuju ecosystem (React + TypeScript).

Readme

fuju-auth-react

Fuju エコシステム向けの認証フロントエンド(React + TypeScript)。AuthCore(バックエンド)との Cookie ベースのセッションをクライアント側で橋渡しし、unstyled なコンポーネントとフックを提供する。<AuthProvider> をツリーの最上位に置き、<AuthGuard> で守られた子要素では useAuth() から常に認証済みユーザーが取れる。

  • CSS を出さない。 className / style / CSS カスタムプロパティでスタイルを当てる。
  • ルーター非依存。 React Router、Next.js App Router、TanStack Router などと組み合わせられる。
  • トークンハンドリング内蔵。 アクセストークンはメモリ、リフレッシュトークンは HttpOnly Cookie、サイレントリフレッシュはタイマー、ログアウトはタブ間でブロードキャスト。
  • 依存が軽い。 peerDependenciesreact / react-dom のみ。ランタイム dependencies なし。

インストール

npm install fuju-auth-react

peerDependenciesreact ^18 || ^19react-dom も同様)。

Quick Start

AuthProvider + AuthGuard + useAuth の最小構成。

import { AuthGuard, AuthProvider, useAuth } from 'fuju-auth-react';

function Dashboard() {
  const { user, logout } = useAuth();
  return (
    <>
      <img src={user.iconUrl ?? '/default-avatar.png'} alt="" />
      <p>こんにちは、{user.displayName} さん</p>
      <p>ID: {user.id}</p>
      <button onClick={logout}>ログアウト</button>
    </>
  );
}

export function App() {
  return (
    <AuthProvider
      config={{
        baseURL: 'https://auth.fuju.example.com',
        onUnauthenticatedNavigate: (path) => window.location.assign(path),
      }}
    >
      <AuthGuard>
        <Dashboard />
      </AuthGuard>
    </AuthProvider>
  );
}

<AuthGuard> の内側では useAuth().user は常に非 null。ガードの外側(共有ヘッダーなど)で条件分岐したい場合は useAuthStatus を使う。

プロバイダ

AuthProvider

React ツリーの最上位に置く DI ルート。AuthConfig を受け取り、内部ストアの生成・bootstrap(Cookie からのセッション復元)・silent refresh・タブ間同期を担当する。config.loadingFallback は bootstrap 中のプレースホルダーとして描画される。props 型は AuthProviderProps

リロードやブラウザ再起動をまたいだセッション保持の長さは、AuthCore が発行するリフレッシュ Cookie の Max-Age / SameSite / Secure / Path に依存する。フロント側ではアクセストークンをメモリにしか置かないため、Max-Age が無い(session cookie)と必ずブラウザ再起動でログアウトされる。保持期間の要件は AuthCore 側と合わせて設計する。

import { useAuthStatus, type SocialProvider } from 'fuju-auth-react';

export function SocialCallback({
  provider,
  searchParams,
}: {
  provider: SocialProvider;
  searchParams: URLSearchParams;
}) {
  const { completeSocialCallback } = useAuthStatus();
  useEffect(() => {
    const state = searchParams.get('state');
    const code = searchParams.get('code');
    if (!state || !code) return;
    void completeSocialCallback(provider, { state, code });
  }, []);
  return <p>サインイン中…</p>;
}

セッション保持の切り分け(onDiagnostic)

「リロード後に MFA 画面に戻る」「毎回ログイン画面に飛ばされる」などセッション保持の問題が出たときは、AuthConfig.onDiagnostic を噛ませると bootstrap / refresh / login / MFA verify / 401 ハンドリングの進行を観測できる。Network タブの Set-Cookie / リクエスト Cookie ヘッダと突き合わせる用途を想定している。

<AuthProvider
  config={{
    baseURL: 'https://auth.fuju.example.com',
    onDiagnostic: (ev) => {
      // 副作用の無い軽い実装が望ましい(console.log / バッファリングのみ)。
      console.log('[fuju-auth]', ev);
    },
  }}
>
  {/* ... */}
</AuthProvider>

観測できる variant は AuthDiagnosticEventsrc/types.ts)を参照。現状は bootstrap-* / refresh-* / session-hint-* / unauthorized-from-api / login-success / mfa-verify-success が発火する。union は open ended なので、将来新しい variant を追加しても型を広げるだけで済む(未知の type は無視する実装を推奨)。

Cookie 属性の確認ポイント(Max-Age / SameSite=None; Secure / Path=/ / Domain / HttpOnly)は docs/frontend-flow.md §5.3.5 にチェックリストがある。

スキーマ定義

以下は src/** からの型定義の転載。JSDoc コメントはコード側と同じものを保持している。

AuthConfig

src/types.ts

export interface AuthConfig {
  /** AuthCore のベース URL。例: `https://auth.fuju.example.com`。 */
  baseURL: string;
  /**
   * 未認証状態で AuthGuard に到達したときに呼ばれる。ホスト側のルーターで
   * `/login` などへ遷移させるために使う。未指定なら AuthGuard が組み込みの
   * LoginForm を直接描画する。
   */
  onUnauthenticatedNavigate?: (path: string) => void;
  /**
   * ソーシャルログインのコールバック URL。AuthCore 側の OAuth クライアントに
   * 登録した値と一致させる。未指定なら `${location.origin}/auth/callback/:provider`。
   */
  socialRedirectURI?: string;
  /**
   * デフォルト UI に並べるソーシャルログインボタンの一覧。`['google']` を渡すと
   * LoginForm / RegisterForm に「Googleログイン」「Googleで登録」ボタンが並ぶ。
   * 未指定 or 空配列ならソーシャルボタンは描画されない(メール + パスワードのみ)。
   * AuthGuard が `loginWithSocial` を自動で配線するため、コンシューマー側での
   * ハンドラ実装は不要。ただし `loginFormComponent` / `registerFormComponent` を
   * カスタム差し替えした場合は `providers` と `loginWithSocial` を自力で渡す。
   */
  providers?: readonly SocialProvider[];
  /** fetch 実装の注入。テストで MSW を差し込むときなどに使用。 */
  fetch?: typeof fetch;
  /** silent refresh のタイマーを無効化する。テスト・SSR 検証で使うことがある。 */
  disableSilentRefresh?: boolean;
  /** bootstrap 実行中に描画されるプレースホルダー。 */
  loadingFallback?: ReactNode;
  /** 組み込みの `MFAChallenge` を差し替える。 */
  mfaChallengeComponent?: ComponentType<MFAChallengeProps>;
  /** 組み込みの `MFASetupWizard` を差し替える。 */
  mfaSetupComponent?: ComponentType<MFASetupWizardProps>;
  /** 組み込みの `LoginForm` を差し替える。差し替え時は `providers` の配線も自前で行う。 */
  loginFormComponent?: ComponentType<LoginFormProps>;
  /** 組み込みの `RegisterForm` を差し替える。差し替え時は `providers` の配線も自前で行う。 */
  registerFormComponent?: ComponentType<RegisterFormProps>;
  /**
   * 組み込みの `SocialSignupPublicIdForm` を差し替える。ソーシャル経由で初回
   * サインアップしたユーザーに対して公開 ID を設定させる UI をカスタマイズする。
   */
  socialSignupComponent?: ComponentType<SocialSignupPublicIdFormProps>;
  /**
   * bootstrap / refresh / 401 ハンドリングなどの内部イベントを受け取る診断フック。
   * 未指定なら何も通知しない。主にリロード直後のログアウト問題など、サーバー側の
   * Cookie 設定とフロントの挙動を突き合わせるための観測点として使う。`console.log`
   * に流して Network タブの `Set-Cookie` と突き合わせる用途を想定しているため、
   * 副作用の無い軽い実装が望ましい。
   */
  onDiagnostic?: (event: AuthDiagnosticEvent) => void;
}

AuthProviderProps

src/AuthProvider.tsx

export interface AuthProviderProps {
  config: AuthConfig;
  children: ReactNode;
}

AuthGuardProps

src/AuthGuard.tsx

export interface AuthGuardProps {
  children: ReactNode;
  required?: boolean;
  enforceMFA?: boolean;
  fallback?: ReactNode;
}

User

src/types.ts

export interface User {
  readonly id: string;
  readonly publicId: string;
  readonly displayName: string;
  readonly email: string;
  readonly iconUrl: string | null;
  readonly mfaEnabled: boolean;
  readonly mfaVerified: boolean;
  readonly linkedProviders: readonly SocialProvider[];
  readonly createdAt: string;
}

AuthError

src/types.ts

export interface AuthError extends Error {
  readonly code: string;
  readonly status: number;
  readonly retryAfterSec?: number;
}

AuthStatus

src/types.ts

export type AuthStatus =
  | 'idle'
  | 'authenticating'
  | 'authenticated'
  | 'mfa_required'
  | 'unauthenticated'
  | 'error';

SocialProvider

src/types.ts

export type SocialProvider = 'google' | 'twitch' | 'x';

LoginResult

src/types.ts

export type LoginResult = { kind: 'authenticated'; user: User } | { kind: 'mfa_required' };

MFASetupResult

src/types.ts

export interface MFASetupResult {
  readonly secret: string;
  readonly qrCodeDataURL: string;
  readonly recoveryCodes: readonly string[];
}

LoginFormProps

src/types.ts

export interface LoginFormProps {
  login: (input: { identifier: string; password: string }) => Promise<LoginResult>;
  loginWithSocial: (provider: SocialProvider) => void;
  register: (input: { email: string; password: string; publicId: string }) => Promise<User>;
  providers?: readonly SocialProvider[];
  onRegisterClick?: () => void;
  className?: string;
  style?: CSSProperties;
}

RegisterFormProps

src/types.ts

export interface RegisterFormProps {
  register: (input: { email: string; password: string; publicId: string }) => Promise<User>;
  login?: (input: { identifier: string; password: string }) => Promise<LoginResult>;
  loginWithSocial?: (provider: SocialProvider) => void;
  providers?: readonly SocialProvider[];
  onLoginClick?: () => void;
  onSuccess?: (user: User) => void;
  className?: string;
  style?: CSSProperties;
}

MFAChallengeProps

src/types.ts

export interface MFAChallengeProps {
  verify: (input: { code?: string; recoveryCode?: string }) => Promise<void>;
  attempts: number;
  cancel: () => void;
  className?: string;
  style?: CSSProperties;
}

MFASetupWizardProps

src/types.ts

export interface MFASetupWizardProps {
  onComplete?: () => void;
  className?: string;
  style?: CSSProperties;
}

SocialSignupPublicIdFormProps

src/types.ts

export interface SocialSignupPublicIdFormProps {
  user: User;
  submit: (publicId: string) => Promise<void>;
  skip: () => void;
  className?: string;
  style?: CSSProperties;
}

AuthErrorFallbackProps

src/components/AuthErrorFallback.tsxsrc/types.ts からは export されていないが利用者向けの型として転載)

export interface AuthErrorFallbackProps {
  error: AuthError | null;
  onRetry?: () => void;
  className?: string;
  style?: CSSProperties;
}

ProfileEditorProps

src/components/ProfileEditor.tsxsrc/types.ts からは export されていないが利用者向けの型として転載)

export interface ProfileEditorProps {
  className?: string;
  style?: CSSProperties;
}

AuthContextAuthenticated

src/hooks/useAuth.tsuseAuth の戻り値型)

export interface AuthContextAuthenticated {
  status: 'authenticated';
  user: User;

  logout: () => Promise<void>;

  refreshProfile: () => Promise<User>;
  updatePublicID: (next: string) => Promise<User>;
  updateIcon: (file: File) => Promise<User>;

  setupMFA: () => Promise<MFASetupResult>;
  enableMFA: (code: string) => Promise<User>;
  disableMFA: (code: string) => Promise<User>;

  connectSocial: (provider: SocialProvider) => void;
  disconnectSocial: (provider: SocialProvider) => Promise<User>;
}

AuthContextAll

src/hooks/useAuthStatus.ts

export type AuthContextAll =
  | { status: 'idle' | 'authenticating'; user: null }
  | { status: 'authenticated' | 'mfa_required'; user: User | null }
  | { status: 'unauthenticated' | 'error'; user: null; error?: AuthError };

AuthStatusActions

src/hooks/useAuthStatus.ts

export interface AuthStatusActions {
  login: (input: { identifier: string; password: string }) => Promise<LoginResult>;
  loginWithSocial: (provider: SocialProvider) => void;
  register: (input: { email: string; password: string; publicId: string }) => Promise<User>;
  logout: () => Promise<void>;
  verifyMFA: (input: { code?: string; recoveryCode?: string }) => Promise<void>;
  cancelMFA: () => void;
  completeSocialCallback: (
    provider: SocialProvider,
    params: { state: string; code: string },
  ) => Promise<User>;
  confirmSocialSignupPublicId: (publicId: string) => Promise<User>;
  skipSocialSignupPublicId: () => void;
}

UseAuthStatusReturn

src/hooks/useAuthStatus.tsuseAuthStatus の戻り値型)

export type UseAuthStatusReturn = AuthContextAll &
  AuthStatusActions & {
    mfaAttempts: number;
    needsPublicIdSetup: boolean;
  };

ErrorCode

src/ErrorCodes.tsErrorCodes の value union)

export const ErrorCodes = {
  INVALID_CREDENTIALS: 'INVALID_CREDENTIALS',
  USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
  USER_NOT_FOUND: 'USER_NOT_FOUND',
  ACCOUNT_LOCKED: 'ACCOUNT_LOCKED',
  EMAIL_NOT_VERIFIED: 'EMAIL_NOT_VERIFIED',
  PUBLIC_ID_ALREADY_EXISTS: 'PUBLIC_ID_ALREADY_EXISTS',
  PUBLIC_ID_RESERVED: 'PUBLIC_ID_RESERVED',
  PUBLIC_ID_FORMAT_INVALID: 'PUBLIC_ID_FORMAT_INVALID',
  EMAIL_INVALID: 'EMAIL_INVALID',
  PASSWORD_TOO_SHORT: 'PASSWORD_TOO_SHORT',
  TOKEN_EXPIRED: 'TOKEN_EXPIRED',
  TOKEN_INVALID: 'TOKEN_INVALID',
  TOKEN_REVOKED: 'TOKEN_REVOKED',
  TOKEN_MALFORMED: 'TOKEN_MALFORMED',
  MFA_REQUIRED: 'MFA_REQUIRED',
  MFA_NOT_ENABLED: 'MFA_NOT_ENABLED',
  MFA_ALREADY_ENABLED: 'MFA_ALREADY_ENABLED',
  TOTP_CODE_INVALID: 'TOTP_CODE_INVALID',
  RECOVERY_CODE_INVALID: 'RECOVERY_CODE_INVALID',
  CLIENT_INVALID: 'CLIENT_INVALID',
  CLIENT_NOT_FOUND: 'CLIENT_NOT_FOUND',
  SOCIAL_PROVIDER_INVALID: 'SOCIAL_PROVIDER_INVALID',
  SOCIAL_AUTH_FAILED: 'SOCIAL_AUTH_FAILED',
  INVALID_REQUEST: 'INVALID_REQUEST',
  MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
  RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
  FILE_TOO_LARGE: 'FILE_TOO_LARGE',
  FILE_FORMAT_INVALID: 'FILE_FORMAT_INVALID',
  INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
  SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
  METHOD_NOT_ALLOWED: 'METHOD_NOT_ALLOWED',
  NOT_IMPLEMENTED: 'NOT_IMPLEMENTED',
  NETWORK_ERROR: 'NETWORK_ERROR',
} as const;

export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];

ライセンス

MIT