gorilla/csrf

repository·main·Indexed 22 days ago

https://github.com/gorilla/csrf

An HTTP middleware library for Go web applications that provides cross-site request forgery (CSRF) protection. It features a 'whitelist only' approach, requiring tokens for mutating methods (POST, PUT, PATCH, DELETE) while exempting safe methods. The library provides tools for protecting HTML forms via csrf.TemplateField and JSON APIs via csrf.Token, and is compatible with any Go web framework implementing the http.Handler interface, including Gin, Echo, and the Gorilla toolkit.

Tokens
5.4K
Snippets
23
Records
30
Agent score
79%

What's inside gorilla/csrf

  1. Overview of gorilla/csrf features

    main

    gorilla/csrf is an HTTP middleware library providing Cross-Site Request Forgery (CSRF) protection. It provides three primary components:

    • csrf.Protect: A middleware/handler that applies CSRF protection to routes attached to a router or sub-router.
    • csrf.Token: A function used to retrieve the CSRF token to be included in a response (e.g., within an HTML form or a JSON response body).
    • csrf.TemplateField: A helper for use with Go's html/template package. It allows you to replace a {{ .csrfField }} template tag with a hidden <input> field containing the CSRF token.
  2. How gorilla/csrf works (Design Principles)

    main

    The library implements a 'whitelist only' approach to CSRF protection:

    • Safe Methods: Token validation is NOT enforced for GET, HEAD, OPTIONS, and TRACE requests.
    • Mutating Methods: Validation is required for POST, PUT, PATCH, and DELETE.
    • Token Mechanism: It generates unique-per-request (masked) tokens to mitigate BREACH attacks. The 'base' unmasked token is stored in the session, allowing multiple browser tabs to work correctly.
    • Security Defaults: Cookies are HttpOnly and Secure (HTTPS only) by default. They are authenticated using the securecookie library.
    • Inspection Order: The middleware inspects the HTTP headers first, then the form body.
  3. Protect HTML Forms with CSRF

    main

    For traditional HTML forms, you need to inject a CSRF token into the form.

    1. In your handler: Use csrf.TemplateField(r) to generate the necessary HTML input field. Pass this to your template engine.
    2. In your template: Use the key provided by csrf.TemplateTag (e.g., {{ .csrfField }}) to render the hidden input.

    Note: The middleware consumes the request body if the token is passed via POST form values. If your handler also needs to read the body, you must insert a middleware to capture the body earlier in the chain.

    func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
        // Inject the CSRF token into the template
        t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
            csrf.TemplateTag: csrf.TemplateField(r),
        })
    }
  4. Ensure CSRF and CORS compatibility for JavaScript frontends

    main

    To use a JavaScript frontend with a gorilla/csrf protected backend, you must satisfy both CORS and CSRF requirements:

    1. CORS Configuration

    The frontend must be served from a domain that is explicitly allowed by the backend's CORS configuration.

    • For the provided backend examples, http://localhost* is typically allowed.
    • If you need a local server to host your HTML and JavaScript, use the example server located in examples/javascript-frontends/example-frontend-server.

    2. CSRF Token Handling

    The frontend must use the specific HTTP headers configured on the backend to send and receive tokens. The backend uses the Gorilla csrf.RequestHeader setting to define these.

    • Sending Tokens: The frontend must include the token in the request header (e.g., X-CSRF-Token).
    • Receiving Tokens: The backend must expose the header via CORS so the client can read it.

    Important Note on Header Casing: Some JavaScript HTTP clients automatically lowercase all received headers. When reading the CSRF token from a response, you may need to access it using the lowercase key "x-csrf-token" instead of the standard casing.

  5. Protect JavaScript Applications and JSON APIs

    main

    When using frontend frameworks (React, Angular, etc.) with a JSON API, follow these steps:

    1. Server-side: In your GET handlers, retrieve the token using csrf.Token(r) and set it in a response header (e.g., X-CSRF-Token).
    2. Client-side: Read the token from the response header and include it in the X-CSRF-Token header for all subsequent mutating requests (POST, PUT, DELETE, etc.).
    3. Cross-Domain: If your JS app is hosted on a different domain than your API, use csrf.TrustedOrigins([]string{"your.domain.com"}) when calling csrf.Protect to allow cross-origin requests.

    Example using Axios:

    // Read token from a hidden input or response header
    let csrfToken = document.getElementsByName("gorilla.csrf.Token")[0].value
    
    const instance = axios.create({
      baseURL: "https://example.com/api/",
      headers: { "X-CSRF-Token": csrfToken }
    })
    // Server-side: Providing the token via header
    func GetUser(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-CSRF-Token", csrf.Token(r))
        // ... return JSON
    }
  6. Use API Backends with JavaScript Frontends

    main

    The examples in examples/api-backends provide working backend APIs protected by CSRF. These are designed to be compatible with the JavaScript frontend examples found in examples/javascript-frontends.

    To ensure successful communication between a browser-based JavaScript client and these backends, the examples include both:

    1. CSRF protection: To prevent cross-site request forgery.
    2. CORS configuration: Required to allow the browser to send CSRF cookies and headers to a different origin.

    For specific requirements regarding CORS and CSRF configuration compatibility, refer to the documentation in examples/javascript-frontends/README.md.

  7. Initialize gorilla/csrf middleware

    main

    To protect your application, wrap your router with the csrf.Protect middleware. You must provide a 32-byte authentication key.

    Key Requirements:

    • Must be exactly 32 bytes long.
    • Must persist across application restarts (do not generate a random key on every startup, or existing cookies will become invalid).
    • Must be kept secret (do not hardcode it in source code).

    If developing locally over plain HTTP, use csrf.Secure(false) to prevent the middleware from requiring HTTPS.

    CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
    http.ListenAndServe(":8000", CSRF(r))
  8. Configure CSRF Options

    main

    You can customize the behavior of the middleware using several options passed to csrf.Protect:

    • csrf.SameSite(mode): Sets the SameSite attribute for the cookie. Use csrf.SameSiteStrictMode or csrf.SameSiteLaxMode.
    • csrf.Path(path): Sets the cookie path. Use csrf.Path("/") to ensure the cookie is available across all application paths.
    • csrf.TrustedOrigins(origins): A list of allowed origins for cross-domain requests.
    • csrf.RequestHeader(name): The HTTP header name to inspect for the token (defaults to X-CSRF-Token).
    • csrf.FieldName(name): The form field name to inspect for the token.
    • csrf.ErrorHandler(handler): A custom http.HandlerFunc to execute when CSRF validation fails.
    CSRF := csrf.Protect(
        []byte("32-byte-long-auth-key"),
        csrf.RequestHeader("Authenticity-Token"),
        csrf.FieldName("authenticity_token"),
        csrf.ErrorHandler(http.HandlerFunc(myErrorHandler)),
        csrf.SameSite(csrf.SameSiteLaxMode),
        csrf.Path("/"),
    )
  9. Configure CSRF protection using functional options

    main

    The gorilla/csrf package uses a functional options pattern to configure the CSRF middleware. You can pass multiple Option functions to the handler initialization to customize cookie behavior, security settings, and validation logic. Options are applied in the order they are provided, with later options overriding earlier ones.

    // Example of applying multiple options
    middleware := csrf.New(nextHandler, 
        csrf.MaxAge(3600), 
        csrf.Secure(false), 
        csrf.SameSite(csrf.SameSiteStrictMode),
    )
  10. Implement a custom CSRF token store

    main

    The csrf package uses a store interface to manage the lifecycle of CSRF tokens. If you need to store tokens in a backend database or a distributed cache instead of a cookie, you must implement this interface.

    To implement a custom store, provide the following two methods:

    1. Get(*http.Request) ([]byte, error): Retrieves the actual CSRF token from the session storage. If using a non-cookie store, the request's cookie should contain a unique ID (e.g., a 256-bit key) that you use to look up the token in your backend.
    2. Save(token []byte, w http.ResponseWriter) error: Persists the token. For non-cookie stores, this method should write a cookie to the http.ResponseWriter containing the unique ID that references the token in your backend.
    type MyCustomStore struct {
        // your backend client/db
    }
    
    func (m *MyCustomStore) Get(r *http.Request) ([]byte, error) {
        // 1. Get the ID from the request cookie
        // 2. Look up the token in your backend using that ID
        // 3. Return the token
        return token, nil
    }
    
    func (m *MyCustomStore) Save(token []byte, w http.ResponseWriter) error {
        // 1. Generate a unique ID (e.g., using csrf.GenerateRandomBytes)
        // 2. Store the token in your backend mapped to that ID
        // 3. Write a cookie to 'w' containing the ID
        return nil
    }