How to Secure Your Hono API with AWS

How to Secure Your Hono API with AWS

This article explains how to implement CORS and CSRF protection in Hono framework, covering security concepts and AWS deployment with practical code examples.
2025.10.21

I recently built a web API endpoint using Hono. I'm not very confident with security, but Hono has some nice middleware. So I'll write about the CORS and CSRF middleware and how to deploy it to AWS.

Apply CORS Middleware

First, let's apply the CORS Middleware to a application. Just add the middleware to the routes you want to protect.

import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono();

app.use('/*', cors());

export const handler = handle(app);

It's that simple. Before I explain the CORS middleware options, let me briefly explain what CORS is.

What is CORS

An origin is define by combination of protocol, host and port number.
Cross-origin requests occur when, for example, a script from https://example.com tries to access https://example.org. Web browsers restrict such cross-origin requests initiated by scripts for security reasons, called Same-Origin Policy.

However, applications often need cross-origin access. CORS allows servers to safely permit this. CORS is an abbreviation of Cross-Origin Resource Sharing. The purpose of CORS is to allow web servers to specify which origins are permitted to access their resources. More specifically, it enables a web server to grant access to selected resources for web applications running on different origins.

The process of a cross-origin request works as follows:

  1. The process of a cross-origin request works as follows:
    GET /sample HTTP/1.1
    Origin: https://application.example.com
    
  2. The server processes the request (regardless of the origin)
  3. The server returns a response with CORS headers:
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: https://application.example.com
    Access-Control-Allow-Methods: GET
    Access-Control-Allow-Headers: Authorization
    
  4. The browser checks the CORS headers:
    • If the origin is allowed, the browser passes the response to JavaScript
    • If not allowed, the browser blocks the response and throws a CORS error

The key point is that CORS involves three parties: the server, the browser, and the application. The browser acts as the enforcer, deciding whether to pass or block the response based on the server's CORS headers.

Important: The server processes the request and returns a response regardless of whether the origin is allowed. CORS enforcement happens entirely in the browser.

setting CORS Middleware

CORS is not a mechanism to prevent requests from reaching the server, even if the browser blocks the response, the server still processes the request. To prevent unnecessary processing, make sure the CORS middleware is called before your route handlers. This allows the server to handle preflight requests (OPTIONS) early and return appropriate CORS headers without executing any business logic in your route handlers.

To set CORS headers and reject invalid requests, configure the middleware as shown below:

import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono();

app.use(
	"/*",
	cors({
		origin: "http:localhost:3000",
		allowMethods: ["POST", "GET", "OPTIONS"],
		// Default is an empty array. Specify headers your app uses.
		allowHeaders: ["Authorization"],
		// Headers that can be accessed by client-side scripts.
		exposeHeaders: ["Authorization"],
		// Set Access-Control-Allow-Credentials header.
		credentials: true,
	}),
);

app.get("/", (c) => {
	return c.text("Hello Hono!");
});

Apply CSRF Protection Middleware

Just apply the CSRF Protection to the application, which enable protection quickly.

import { Hono } from 'hono'
import { csrf } from 'hono/csrf'

const app = new Hono();
// Protect whole of an application
app.use(csrf())

It's that simple. Before I explain theCSRF Protection Middleware options, let me briefly explain what CSRF is and how to protect.

What is CSRF

CSRF is an abbreviation of Cross-Site Request Forgery. It is an attack where an attacker tricks a user's browser into making an unwanted HTTP request to a target application. Since the HTTP request automatically includes credentials (such as cookies), this enables an attacker to perform harmful actions on behalf of the victim.

Cookies are automatically sent to the server, this is the key point of CSRF attacks. An attacker doesn't need to know the credentials, but only needs to make the victim's browser send the request with correct information.
The victim's browser, unaware of the malicious intent, includes authentication cookies automatically when making the forged request. Therefore, the server cannot distinguish between legitimate requests from the user and forged requests triggered by the attacker's site. This is why protects against CSRF attacks. Hono checking both the Origin header and the Sec-Fetch-Site header.

Origin header / Sec-Fetch-Site header
By default, Hono's CSRF protection only allows requests from the same origin. The middleware validates both the Origin header and the Sec-Fetch-Site header to determine whether to allow or block a request.

A request is allowed only when both conditions are satisfied:

https://github.com/honojs/hono/blob/main/src/middleware/csrf/index.ts#L135-L144

The Sec-Fetch-Site header is automatically added by modern browsers and indicates the relationship between the request origin and the target origin:

  • same-origin: Request from the exact same origin (scheme, host, and port match)
  • same-site: Request from the same site but different subdomain
  • cross-site: Request from a completely different site (typically blocked for CSRF protection)
  • none: Request initiated outside of a browsing context (e.g., direct URL entry, bookmark)
    • Verify that the request originated from a browser

setting CSRF Protection Middleware

You can customize the CSRF protection behavior using the origin and secFetchSite parameters:

import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono();

app.use(
  csrf({
    origin: [
      'http://localhost:3000',
    ],
    secFetchSite: 'cross-site'
  })
)
  • origin: Array of allowed origins for the request
  • secFetchSite: Allowed values for the Sec-Fetch-Site header (e.g., 'same-origin', 'same-site', 'cross-site', 'none')

With this understanding, here's how I configured Hono for my application:

import { Hono } from "hono";
import { handle } from "hono/aws-lambda";
import { cors } from "hono/cors";
import { csrf } from "hono/csrf";
import { HTTPException } from "hono/http-exception";

const app = new Hono();

app.use(
	"/*",
	cors({
		origin: "http:localhost:3000",
		allowMethods: ["POST", "GET", "OPTIONS"],
		// Default is empty array. Set if you use headers.
		allowHeaders: ["Authorization"],
		// Expose and can access from scripts.
		exposeHeaders: ["Authorization"],
		// Set Access-Control-Allow-Credentials header.
		credentials: true,
	}),
);
app.use(
	csrf({
		origin: ["http://localhost:3000"],
		secFetchSite: "cross-site",
	}),
);

app.onError((error, c) => {
	return c.json(
		{
			error: error.message || "Internal Server Error",
		},
		error instanceof HTTPException ? error.status : 500,
	);
});

app.get("/", (c) => {
	return c.text("Hello Hono!");
});
app.post("/", (c) => {
	return c.text("accept request !");
});

export const handler = handle(app);

Deploy with AWS CDK

I have prepared a simple CDK stack for demonstration.

import { join } from "node:path";
import {
	Stack as CDKStack,
	CfnOutput,
	Duration,
	type StackProps,
} from "aws-cdk-lib";
import {
	CorsHttpMethod,
	HttpApi,
	HttpMethod,
} from "aws-cdk-lib/aws-apigatewayv2";
import { HttpLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction, OutputFormat } from "aws-cdk-lib/aws-lambda-nodejs";
import type { Construct } from "constructs";

export class Stack extends CDKStack {
	constructor(scope: Construct, id: string, props?: StackProps) {
		super(scope, id, props);

		const handler = new NodejsFunction(this, "SecureHonoHandler", {
			entry: join(__dirname, "..", "..", "api", "src", "index.ts"),
			handler: "handler",
			runtime: Runtime.NODEJS_LATEST,
			timeout: Duration.seconds(30),
			projectRoot: join(__dirname, "..", ".."),
			depsLockFilePath: join(__dirname, "..", "..", "package-lock.json"),
			bundling: {
				forceDockerBundling: false,
				target: "node24",
				format: OutputFormat.ESM,
				sourceMap: true,
				banner:
					"import { createRequire } from 'module';const require = createRequire(import.meta.url);",
			},
		});

		const httpApi = new HttpApi(this, "SecureHonoAPI", {
			apiName: "SecureHonoAPI",
			corsPreflight: {
				allowHeaders: ["authorization", "content-type"],
				allowMethods: [CorsHttpMethod.ANY],
				allowOrigins: ["http://localhost:3000"],
			},
		});

		const lambdaIntegration = new HttpLambdaIntegration(
			"SecureHonoIntegration",
			handler,
		);

		httpApi.addRoutes({
			path: "/",
			methods: [HttpMethod.ANY],
			integration: lambdaIntegration,
		});
		httpApi.addRoutes({
			path: "/{proxy+}",
			methods: [HttpMethod.ANY],
			integration: lambdaIntegration,
		});

		new CfnOutput(this, "ApiEndpoint", {
			value: httpApi.apiEndpoint,
		});
	}
}

The key aspects of this configuration are:

  • All requests are proxied to the Lambda function: The API Gateway forwards incoming requests directly to Lambda, where CORS headers are set and applied by the Hono middleware.
  • Preflight requests are handled at the API Gateway level: OPTIONS requests (preflight checks) are intercepted and responded to by API Gateway itself. This prevents unnecessary invocations of the Lambda function, reducing costs and improving response times for CORS preflight checks.

This approach balances flexibility and efficiency—your application logic in Lambda has full control over CORS policies, while API Gateway handles the lightweight preflight requests without invoking your function.

To close

Security implementation can be daunting, especially when you're not deeply familiar with the underlying concepts. However, Hono provides an elegant and straightforward approach to implementing essential security measures through its well-designed middleware system.

References

https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF

https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site

https://hono.dev/docs/middleware/builtin/cors

https://hono.dev/docs/middleware/builtin/csrf

この記事をシェアする

FacebookHatena blogX

関連記事