# Auth Hooks and Rate Limits

Canonical page: https://docs.ovok.com/native-sdk/guide/components/auth/hooks

Use the auth hooks and helpers when a custom form needs to share session state
or implement the same retry behavior as the built-in auth forms. These hooks
require the active client from [`OvokProvider`](/native-sdk/guide/how-to/app-shell).

## Apply rate limits to a custom form [#apply-rate-limits-to-a-custom-form]

The SDK's sign-in, registration, and password-reset buttons already disable
submission during a supported cooldown. For a custom Formik screen, use
`setRateLimitFormError` in the submit handler and `useRateLimitCooldown` in the
rendering component.

```tsx
import { useOvok } from "@ovok/core/next/react";
import { useFormik } from "formik";
import {
  setRateLimitFormError,
  useRateLimitCooldown,
} from "@ovok/native";
import * as Yup from "yup";
import { Button, Text, TextInput } from "react-native-paper";

type RecoveryValues = { email: string; submit: string };

export function CustomRecoveryForm() {
  const client = useOvok();
  const formik = useFormik<RecoveryValues>({
    initialValues: { email: "", submit: "" },
    validationSchema: Yup.object({
      email: Yup.string().email("Enter a valid email").required("Enter an email"),
      submit: Yup.string(),
    }),
    onSubmit: async (values, helpers) => {
      try {
        await client.auth.resetPassword({ email: values.email, type: "Patient" });
      } catch (error) {
        const details = setRateLimitFormError(error, helpers);
        if (!details) helpers.setFieldError("submit", "auth.request-failed");
      }
    },
  });

  const rateLimitUntil = (
    formik.status as { rateLimitUntil?: number } | undefined
  )?.rateLimitUntil;
  const remainingMs = useRateLimitCooldown(rateLimitUntil);

  return (
    <>
      <TextInput
        label="Email"
        value={formik.values.email}
        onChangeText={formik.handleChange("email")}
        onBlur={formik.handleBlur("email")}
        error={!!formik.errors.email}
      />
      {formik.errors.submit && <Text>{formik.errors.submit}</Text>}
      <Button
        disabled={formik.isSubmitting || remainingMs > 0}
        loading={formik.isSubmitting}
        onPress={() => void formik.submitForm()}
      >
        {remainingMs > 0
          ? `Try again in ${Math.ceil(remainingMs / 1000)}s`
          : "Continue"}
      </Button>
    </>
  );
}
```

Localize the button label and error keys for your app. `setRateLimitFormError`
stores an absolute `rateLimitUntil` timestamp in Formik status, sets the
`submit` error to `auth.rate-limit`, and returns the parsed details.

## Recognized response shapes [#recognized-response-shapes]

`getRateLimitDetails(error)` recognizes `code: "rate_limit"`, `status: 429`,
and `response.status: 429`. It returns a message key and retry delay. A positive
`retryAfterMs` from `@ovok/core`'s rate-limit error is used when present;
otherwise `DEFAULT_RATE_LIMIT_RETRY_MS` is `60_000`. Unrecognized errors return
`undefined`, so your form should handle them normally.

The built-in sign-in, registration, and password-reset forms use the same server
delay for their submit cooldown. A wrong password answers 401 (older servers answered 400); both show the
invalid-credentials message. A password the server refuses, such as one shorter than 12 characters or a common
one, answers 422 and its message is shown on the password field. Field errors come from `@ovok/core`'s
`readOvokError`, so each one has a `path`; update `@ovok/core` to read the field errors of newer servers. Their `onError` callback receives the parsed
`SignInError`, including `retryAfterMs` when the server provides it.

`useRateLimitCooldown(until)` takes the absolute timestamp from Formik status,
returns remaining milliseconds, and updates once per second while active. It
returns `0` when no cooldown remains.

## Session state [#session-state]

`useSession` exposes the current auth status, profile, sessions, refresh
operations, logout, and session revocation. It does not protect routes or choose
where the app navigates. It also exposes `switching` during account changes and
`reason: "session-ended"` when the active login ends unexpectedly. See
[Session Management](/native-sdk/guide/components/auth/session/) for its lifecycle, error behavior, and backend
requirements.

## Related guides [#related-guides]

- [Sign-in](/native-sdk/guide/components/auth/sign-in/)
- [Registration](/native-sdk/guide/components/auth/register/)
- [Password reset](/native-sdk/guide/components/auth/reset-password/)
