Skip to content
oRPC
Esc
navigateopen⌘Jpreview
On this page

GET Method CSRF Protection Plugin

Use GetMethodCsrfProtectionHandlerPlugin to make the safe GET method as secure as POST by rejecting navigations that may carry SameSite=Lax cookies from another site.

How It Works

Cross-site, browsers withhold explicitly marked SameSite=Lax cookies from unsafe methods such as POST, but still attach them to safe methods like GET on top-level navigations, per RFC 6265bis. The plugin closes that gap by rejecting exactly those navigations with a 403 before routing:

Request from another site Sends SameSite=Lax cookies
link click, redirect, window.open, GET form yes rejected
address bar, bookmark, link from an email or native app yes rejected
fetch, XMLHttpRequest no allowed, CORS governs the response
<img>, <script>, media, prefetch no allowed
<iframe>, <embed>, <object>, which are not top-level no allowed
any method other than GET no ignored

The verdict comes from Fetch Metadata: a GET request is rejected when Sec-Fetch-Site reports cross-site or none on a top-level navigation (Sec-Fetch-Mode: navigate targeting Sec-Fetch-Dest: document). A guarded request stripped of either header is rejected. Only GET needs guarding, since navigations use no method besides GET and the unsafe POST. The result resembles upgrading your cookies to SameSite=Strict for safe methods, and also covers links opened from outside the browser, where browsers attach even Strict cookies.

Cookie-less cross-site requests pass, which makes the plugin the natural safeguard for enabling the GET method. Requests from your own site always pass, including sibling subdomains, since the SameSite cookie model makes the site the trust boundary. Host untrusted content on a separate site, not a subdomain.

Setup

import { class GetMethodCsrfProtectionHandlerPlugin<T extends Context>
Adds Cross-Site Request Forgery (CSRF) protection that makes the safe `GET` method as secure as unsafe ones such as `POST`. It rejects `GET` requests arriving as top-level navigations initiated cross-site or from outside the browser, the only context where another site can make a browser attach `SameSite=Lax` cookies to a safe-method request.
@remarks**Note**: Requests browsers send without `SameSite=Lax` cookies, such as cross-site `fetch` and `<img>`, pass through, so procedures stay reachable from other sites. This safeguard requires authentication cookies explicitly marked `SameSite=Lax` or `SameSite=Strict`, since browsers may attach other cookies to the requests that pass.@see{@link https://orpc.dev/docs/plugins/get-method-csrf-protection GET Method CSRF Protection Plugin}
GetMethodCsrfProtectionHandlerPlugin
} from '@orpc/server/plugins'
import { const RPC_DEFAULT_ALLOW_METHODS: readonly StandardMethod[]
Methods that can invoke procedures by default. Browsers cannot trigger them cross-site without a CORS preflight or an HTML form, unlike `GET`, which a plain `<a>` click or redirect can trigger with `SameSite=Lax` cookies attached. Other methods (`HEAD`, `OPTIONS`, `QUERY`, ...) have safe semantics that should not invoke a procedure that can modify data.
@see{@link https://orpc.dev/docs/rpc/handler#supported-http-methods RPC Handler - Supported HTTP Methods}
RPC_DEFAULT_ALLOW_METHODS
} from '@orpc/server/standard'
const
const handler: RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>
handler
= new
new RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>(router: Router<{
    headers?: IncomingHttpHeaders;
} & object>, options?: NoInfer<RPCHandlerOptions<{
    headers?: IncomingHttpHeaders;
} & object>>): RPCHandler<{
    headers?: IncomingHttpHeaders;
} & object>
Serves an oRPC router over the RPC protocol using the Fetch API (Request/Response), supported by modern runtimes like Deno, Bun, Cloudflare Workers, and browsers.
@see{@link https://orpc.dev/docs/adapters/fetch-api Fetch API Adapter}
RPCHandler
(
const router: {
    planet: {
        list: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            limit: ZodOptional<ZodNumber>;
            cursor: ZodDefault<ZodNumber>;
        }, $strip>, ZodArray<ZodObject<{
            id: ZodNumber;
            name: ZodString;
            description: ZodOptional<ZodString>;
        }, $strip>>, object>;
        find: ImplementedProcedure<{
            headers?: IncomingHttpHeaders;
        } & object, object, ZodObject<{
            id: ZodNumber;
        }, $strip>, ZodObject<...>, object>;
        create: ImplementedProcedure<...>;
    };
}
router
, {
RPCMatcherOptions.allowMethods?: readonly StandardMethod[] | ((method: StandardMethod, procedure: AnyProcedure, path: string[]) => boolean) | undefined
Restricts which HTTP methods can invoke procedures, either with a list of allowed methods or decided per request via a function. Requests using a disallowed method are treated as unmatched. `GET` is excluded by default because it is exposed to Cross-Site Request Forgery (CSRF) attacks.
@defaultRPC_DEFAULT_ALLOW_METHODS (['POST', 'PUT', 'PATCH', 'DELETE'])@see{@link https://orpc.dev/docs/rpc/handler#supported-http-methods RPC Handler - Supported HTTP Methods}
allowMethods
: ['GET', ...const RPC_DEFAULT_ALLOW_METHODS: readonly StandardMethod[]
Methods that can invoke procedures by default. Browsers cannot trigger them cross-site without a CORS preflight or an HTML form, unlike `GET`, which a plain `<a>` click or redirect can trigger with `SameSite=Lax` cookies attached. Other methods (`HEAD`, `OPTIONS`, `QUERY`, ...) have safe semantics that should not invoke a procedure that can modify data.
@see{@link https://orpc.dev/docs/rpc/handler#supported-http-methods RPC Handler - Supported HTTP Methods}
RPC_DEFAULT_ALLOW_METHODS
],
FetchHandlerOptions<{ headers?: IncomingHttpHeaders; } & object>.plugins?: FetchHandlerPlugin<{
    headers?: IncomingHttpHeaders;
} & object>[] | undefined
plugins
: [
new
new GetMethodCsrfProtectionHandlerPlugin<{
    headers?: IncomingHttpHeaders;
} & object>(): GetMethodCsrfProtectionHandlerPlugin<{
    headers?: IncomingHttpHeaders;
} & object>
Adds Cross-Site Request Forgery (CSRF) protection that makes the safe `GET` method as secure as unsafe ones such as `POST`. It rejects `GET` requests arriving as top-level navigations initiated cross-site or from outside the browser, the only context where another site can make a browser attach `SameSite=Lax` cookies to a safe-method request.
@remarks**Note**: Requests browsers send without `SameSite=Lax` cookies, such as cross-site `fetch` and `<img>`, pass through, so procedures stay reachable from other sites. This safeguard requires authentication cookies explicitly marked `SameSite=Lax` or `SameSite=Strict`, since browsers may attach other cookies to the requests that pass.@see{@link https://orpc.dev/docs/plugins/get-method-csrf-protection GET Method CSRF Protection Plugin}
GetMethodCsrfProtectionHandlerPlugin
(),
], })

Mark authentication cookies SameSite=Lax or SameSite=Strict explicitly. Do not rely on browser defaults: only Chrome treats unmarked cookies as Lax, while Firefox and Safari treat them like SameSite=None, and Chrome still sends fresh unmarked cookies on cross-site POST for two minutes.

With SameSite=None or unmarked cookies, cross-site requests the plugin allows, such as fetch and <img>, can still carry them. In that case, fix the cookie attribute or add a synchronizer token.

Limitations

  • Fetch Metadata is Baseline widely available, supported by every major browser since Safari 16.4 in March 2023. Older browsers and header-stripping proxies pass through unchecked.
  • Browsers send Fetch Metadata only to trustworthy URLs: HTTPS and localhost. Over plain HTTP the headers are absent while cookies are not, so every request passes, and localhost qualifying hides this in development.
  • Users cannot open guarded procedures by typing the URL, following a bookmark, or clicking a link, since those navigations look identical to a forged one. Test with curl or a same-origin page instead.
  • Prefer SameSite=Strict or a synchronizer token for high-value requests.

Learn More

Learn more about the attack this plugin prevents on MDN and in the OWASP CSRF Prevention Cheat Sheet. For implementation details, see the source code.

Last updated on August 12, 2026

Was this page helpful?