Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions apps/nextjs/app-router/src/app/client/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { useAuth } from "@aura-stack/next/client"
import { useSession, useAuthActions } from "@aura-stack/next/client"
import { EditProfile } from "@/components/edit-profile"
import type { SubmitEvent } from "react"

export const AuthClientPage = () => {
const { session, status, isPending, signIn, signOut, signInCredentials, updateSession } = useAuth()
const { signIn, signInCredentials, updateSession, signOut, isPending } = useAuthActions()
const { session, status } = useSession()
const isAuthenticated = status === "authenticated"

const handleSignInCredentials = async (event: SubmitEvent<HTMLFormElement>) => {
Expand Down Expand Up @@ -118,7 +119,7 @@ export const AuthClientPage = () => {
variant="outline"
disabled={isPending}
key={provider}
onClick={() => signIn(provider.toLowerCase())}
onClick={async () => await signIn(provider.toLowerCase(), { redirect: true })}
>
Sign In with {provider}
</Button>
Expand All @@ -137,6 +138,7 @@ export const AuthClientPage = () => {
type="text"
id="username"
name="username"
aria-label="Username"
className="w-full h-9 mt-1 font-medium border border-input rounded-none bg-background hover:text-accent-foreground hover:bg-input/50 focus:outline-1"
/>
</div>
Expand All @@ -148,6 +150,7 @@ export const AuthClientPage = () => {
type="password"
id="password"
name="password"
aria-label="Password"
className="w-full h-9 mt-1 font-medium border border-input rounded-none bg-background hover:text-accent-foreground hover:bg-input/50 focus:outline-1"
/>
</div>
Expand Down
5 changes: 3 additions & 2 deletions apps/nextjs/app-router/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import Link from "next/link"
import { useState } from "react"
import { Menu, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useAuth } from "@aura-stack/next/client"
import { useAuthActions, useSession } from "@aura-stack/next/client"

export const Header = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { status, isPending, signOut, signIn } = useAuth()
const { isPending, signOut, signIn } = useAuthActions()
const { status } = useSession()
const isAuthenticated = status === "authenticated"

const handleSignOut = async () => {
Expand Down
5 changes: 3 additions & 2 deletions apps/nextjs/pages-router/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { useRouter } from "next/router"
import { useState } from "react"
import { Menu, X } from "lucide-react"
import { Button } from "@/components/ui/button"
import { useAuth } from "@aura-stack/next/client"
import { useAuthActions, useSession } from "@aura-stack/next/client"

export const Header = () => {
const router = useRouter()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { status, isPending, signOut, signIn } = useAuth()
const { status } = useSession()
const { isPending, signOut, signIn } = useAuthActions()
const isAuthenticated = status === "authenticated"

const handleSignOut = async () => {
Expand Down
6 changes: 4 additions & 2 deletions apps/nextjs/pages-router/src/pages/client/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Link from "next/link"
import Image from "next/image"
import { Button } from "@/components/ui/button"
import { useAuth } from "@aura-stack/next/client"
import { useAuthActions, useSession } from "@aura-stack/next/client"
import { EditProfile } from "@/components/edit-profile"
import type { SubmitEvent } from "react"

export default function AuthClientPage() {
const { session, status, isPending, signIn, signOut, signInCredentials, updateSession } = useAuth()
const { session, status } = useSession()
const { isPending, signIn, signOut, signInCredentials, updateSession } = useAuthActions()
const isAuthenticated = status === "authenticated"

const handleSignInCredentials = async (event: SubmitEvent<HTMLFormElement>) => {
Expand All @@ -20,6 +21,7 @@ export default function AuthClientPage() {
username,
password,
},
redirect: true,
redirectTo: "/client",
})
}
Expand Down
5 changes: 3 additions & 2 deletions apps/react-router/app/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { useState } from "react"
import { Menu, X } from "lucide-react"
import { Button } from "~/components/ui/button"
import { Link, useRevalidator } from "react-router"
import { useAuth } from "@aura-stack/react-router/client"
import { useAuthActions, useSession } from "@aura-stack/react-router/client"

export const Header = () => {
const revalidator = useRevalidator()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { status, isPending, signOut, signIn } = useAuth()
const { status } = useSession()
const { isPending, signOut, signIn } = useAuthActions()
const isAuthenticated = status === "authenticated"

const handleSignOut = async () => {
Expand Down
7 changes: 5 additions & 2 deletions apps/react-router/app/routes/client/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Link, useRevalidator } from "react-router"
import { Button } from "~/components/ui/button"
import { useAuth } from "@aura-stack/react-router/client"
import { useAuthActions, useSession } from "@aura-stack/react-router/client"
import { EditProfile } from "~/components/edit-profile"
import type { SubmitEvent } from "react"

export const AuthClientPage = () => {
const revalidator = useRevalidator()
const { session, status, isPending, signIn, signOut, signInCredentials, updateSession } = useAuth()
const { session, status } = useSession()
const { isPending, signIn, signOut, signInCredentials, updateSession } = useAuthActions()
const isAuthenticated = status === "authenticated"

const handleSignInCredentials = async (event: SubmitEvent<HTMLFormElement>) => {
Expand Down Expand Up @@ -130,6 +131,7 @@ export const AuthClientPage = () => {
type="text"
id="username"
name="username"
aria-label="Username"
className="w-full h-9 mt-1 font-medium border border-input rounded-none bg-background hover:text-accent-foreground hover:bg-input/50 focus:outline-1"
/>
</div>
Expand All @@ -141,6 +143,7 @@ export const AuthClientPage = () => {
type="password"
id="password"
name="password"
aria-label="Password"
className="w-full h-9 mt-1 font-medium border border-input rounded-none bg-background hover:text-accent-foreground hover:bg-input/50 focus:outline-1"
/>
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
},
})
const json = await response.json()
if ((options?.redirect ?? true) && typeof window !== "undefined" && json?.signInURL) {
if (options?.redirect === true && typeof window !== "undefined" && json?.signInURL) {
window.location.assign(json.signInURL)
}
return json as unknown as SignInReturn<Options>
Expand All @@ -94,7 +94,7 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
},
})
const json = await response.json()
if ((options?.redirect ?? true) && typeof window !== "undefined" && json?.redirectURL) {
if (options?.redirect === true && typeof window !== "undefined" && json?.redirectURL) {
window.location.assign(json.redirectURL)
}
return json as unknown as SignInCredentialsReturn<Options>
Expand Down Expand Up @@ -128,7 +128,7 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
},
})
const json = await response.json()
if ((options.redirect ?? true) && typeof window !== "undefined" && json?.redirectURL) {
if (options?.redirect === true && typeof window !== "undefined" && json?.redirectURL) {
window.location.assign(json.redirectURL)
}
return json as unknown as UpdateSessionReturn<Options, DefaultUser>
Expand All @@ -155,7 +155,7 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
},
})
const json = await response.json()
if ((options?.redirect ?? true) && typeof window !== "undefined" && json?.redirectURL) {
if (options?.redirect === true && typeof window !== "undefined" && json?.redirectURL) {
window.location.assign(json.redirectURL)
}
return json as unknown as SignOutReturn<Options>
Expand Down
23 changes: 23 additions & 0 deletions packages/core/test/client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,29 @@ describe("createAuthClient", () => {
expect(response).toEqual({ signInURL: "https://example.com/oauth" })
})

test("signIn with redirect: false does not navigate", async () => {
vi.stubGlobal("window", { location: { assign: vi.fn() } })

const get = vi.fn().mockResolvedValue(createJSONResponse({ signInURL: "https://example.com/oauth" }))

createClientMock.mockReturnValue({
get,
post: vi.fn(),
})
const client = createAuthClient({ baseURL: "https://example.com" })
await client.signIn("github", { redirect: false })

expect(get).toHaveBeenCalledWith("/signIn/:oauth", {
params: { oauth: "github" },
searchParams: {
// The redirect is set to false in the request to prevent automatic
// redirection by server response by 302 status code.
redirect: false,
},
})
expect(window.location.assign).not.toHaveBeenCalled()
})

test("signInCredentials", async () => {
const post = vi.fn().mockResolvedValue(
createJSONResponse({
Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
export {
createAuthClient,
AuthProvider,
useAuth,
useAuthActions,
useSession,
useSignIn,
useSignInCredentials,
useSignOut,
useUpdateSession,
type AuthProviderProps,
type AuthClientOptions,
} from "@aura-stack/react"
2 changes: 1 addition & 1 deletion packages/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@
"react-router": ">=7.0.0"
},
"packageManager": "pnpm@10.15.0"
}
}
2 changes: 1 addition & 1 deletion packages/react-router/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export {
createAuthClient,
AuthProvider,
useAuth,
useAuthActions,
useSession,
useSignIn,
useSignInCredentials,
Expand Down
2 changes: 2 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

### Added

- Added support for multi-tab synchronization via `BroadcastChannel`, allowing sessions to stay synchronized across browser tabs during the auth flow. Additionally, added a centralized `useAuthActions` hook that re-exports all auth actions (`signIn`, `updateSession`, `signOut`, etc.). [#172](https://github.com/aura-stack-ts/auth/pull/172)

- Updated React context and hooks (`useSignInCredentials`, `useUpdateSession`, and related context actions) to align with the standardized core client API contracts, including the new object-based credentials/session payload shapes and redirect-driven refresh behavior, while simplifying React-side auth type definitions. [#146](https://github.com/aura-stack-ts/auth/pull/146)

- Removed and cleaned up types and functions exported from the index `/` entry point to reduce import noise, and introduced `/identity`, `/crypto`, and `/shared` as direct entry points for specific utilities. [#141](https://github.com/aura-stack-ts/auth/pull/141)
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,4 @@
"@types/react": ">=19.0.0"
},
"packageManager": "pnpm@10.15.0"
}
}
25 changes: 8 additions & 17 deletions packages/react/src/@types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,12 @@ import type { createAuthClient } from "@aura-stack/auth/client"
export type AuthClientInstance<DefaultUser extends User = User> = ReturnType<typeof createAuthClient<DefaultUser>>

/** High-level UI state for whether a session is present, absent, or still being resolved. */
export type AuthStatus = "authenticated" | "unauthenticated" | "loading"
export type AuthStatus = "authenticated" | "unauthenticated" | "pending"

/**
* Full auth surface exposed through a single React context so session state and
* mutations share one source of truth (no duplicate session fetches per subtree).
*/
export type AuthReactContextValue<DefaultUser extends User = User> = {
/** Current session, `null` if unauthenticated, or `undefined` before the first load completes. */
session: Session<DefaultUser> | null | undefined
export interface Context<DefaultUser extends User = User> {
session: Session | null
status: AuthStatus
/** True while a transition updates session state (e.g. after refresh or a non-redirect sign-in). */
isPending: boolean
/** Bound auth HTTP client (same API as `createAuthClient`). */
client: AuthClientInstance<DefaultUser>
/** Re-fetches session from the server and updates context state. */
refresh: () => Promise<void>
signIn: AuthClientInstance<DefaultUser>["signIn"]
signInCredentials: AuthClientInstance<DefaultUser>["signInCredentials"]
signOut: AuthClientInstance<DefaultUser>["signOut"]
updateSession: AuthClientInstance<DefaultUser>["updateSession"]
}

/** Props for {@link AuthProvider}: supply the client and optional SSR session to avoid a flash of loading state. */
Expand All @@ -46,3 +32,8 @@ export type AuthProviderProps<DefaultUser extends User = User> = {
*/
initialSession?: Session<DefaultUser> | null
}

/**
* Triggerable messages for cross-tab session synchronization via BroadcastChannel.
*/
export type BroadcastMessage = { type: "session:update"; payload: Session } | { type: "session:sync" } | { type: "session:clear" }
Loading
Loading