ForgeWP

Login & Logout

Use the built-in form or connect a custom login interface to Auth actions.

Login and logout

Put your account interface inside WpAuthProvider. Use WpLoginForm when its ready-made controls fit the design, or call useWpAuth().login() from your own form.

src/components/AccountLogin.tsx
1import { WpAuthProvider, WpLoginForm } from "@forgewp/auth"
2
3export default function AccountLogin() {
4  return (
5    <WpAuthProvider>
6      <WpLoginForm className="max-w-md" />
7    </WpAuthProvider>
8  )
9}

WpLoginForm accepts className and an optional onSuccess callback. It does not accept a redirectTo prop. For an explicit destination, use the custom login() call below. Cookie-mode production login normally reloads or navigates as part of the action; do not rely on a callback running before that navigation.

Choose your starting point

Table · 3 columns
Approach
Best fit
What you supply
WpLoginForm
A ready-made login interface
Provider scope, placement, and optional className
Custom form with login()
Your own controls, feedback, or destination
Input state, submission handler, and loading/error UI
Keep the first version small

Start with the built-in form to check the account workflow. Switch to a custom form when the interface needs different controls or behavior.

Build a custom form

login(credential, password, redirectUrl?) returns Promise<boolean>. A credential is the username or email entered by the visitor; the active loginField policy decides which is accepted.

src/components/CustomAccountLogin.tsx
1import { useState, type FormEvent } from "react"
2import { WpAuthProvider, useWpAuth } from "@forgewp/auth"
3
4function LoginFields() {
5  const [credential, setCredential] = useState("")
6  const [password, setPassword] = useState("")
7  const { login, loading, error, loginField } = useWpAuth()
8  const label =
9    loginField === "emailOnly"
10      ? "Email"
11      : loginField === "usernameOnly"
12        ? "Username"
13        : "Username or email"
14
15  async function handleSubmit(event: FormEvent<HTMLFormElement>) {
16    event.preventDefault()
17    const signedIn = await login(credential, password)
18    if (signedIn) setPassword("")
19  }
20
21  return (
22    <form onSubmit={handleSubmit}>
23      <label>
24        {label}
25        <input
26          name="credential"
27          type={loginField === "emailOnly" ? "email" : "text"}
28          autoComplete="username"
29          value={credential}
30          onChange={(event) => setCredential(event.target.value)}
31          required
32        />
33      </label>
34      <label>
35        Password
36        <input
37          name="password"
38          type="password"
39          autoComplete="current-password"
40          value={password}
41          onChange={(event) => setPassword(event.target.value)}
42          required
43        />
44      </label>
45      <button type="submit" disabled={loading}>
46        {loading ? "Signing in…" : "Sign in"}
47      </button>
48      {error && <p role="alert">{error}</p>}
49    </form>
50  )
51}
52
53export default function CustomAccountLogin() {
54  return (
55    <WpAuthProvider>
56      <LoginFields />
57    </WpAuthProvider>
58  )
59}

To choose a destination, pass a developer-controlled URL as the third argument, for example login(credential, password, "/account"). Choose a real route in your theme; this argument does not create a WordPress Page. Use your site's actual path when WordPress is installed under a subdirectory. Do not pass unreviewed user input as a redirect destination.

If login fails, it returns false and updates error. Read that error during rendering so it reflects the latest hook state. The form above keeps the visitor's input available to correct; a successful login clears its password state if the current page remains mounted.

Flowchart
Rendering diagram...
Keep navigation intentional

Use a destination supplied by your theme. A redirect URL selects an existing route; it does not create a Page or grant permission to view it.

Sign out

Call logout() from the same provider scope as your account display. It returns Promise<void>; render error for feedback rather than expecting a success boolean. The first-login example shows a complete user display and logout button.

In local preview, signing out clears the browser's mock session. In WordPress cookie mode, Auth sends a POST request to /wp-json/forgewp/v1/auth/logout. A successful response clears local user state and reloads the page. If the server rejects the request or the connection fails, the current user state is retained, an error is shown through the hook, and the page stays available for another attempt.

JWT-mode logout clears the locally stored token and user display. Server-side token revocation depends on the JWT backend; local removal alone does not revoke a previously issued token.

WordPress installed under a subdirectory

Both provider and standalone cookie calls preserve the path from the generated home setting. A home URL such as https://example.test/members/ produces /members/wp-json/forgewp/v1/auth/login. Root installations use /wp-json/....

An explicitly supplied API base takes priority and is normalized without a trailing slash. Supply the WordPress base URL, such as https://backend.test/wordpress, rather than a URL already ending in /wp-json. Cross-origin cookie behavior needs deployment-specific configuration; a working same-origin preview does not establish it.

Check the complete workflow

In preview, test the mock login identifier policy, visible errors, and logout state. On an installed theme, test correct and incorrect credentials, a page reload after login, a rejected logout request, and protected content after successful logout. These are separate checks because the preview does not validate WordPress password hashes or server cookies.