diff --git a/apps/www/components/blog/blog-image.tsx b/apps/www/components/blog/blog-image.tsx index 720ca0c238..4475ba6900 100644 --- a/apps/www/components/blog/blog-image.tsx +++ b/apps/www/components/blog/blog-image.tsx @@ -2,15 +2,24 @@ import { ImageWithBlur } from "../image-with-blur"; export function BlogImage({ imageUrl, + unoptimize, }: { size: "sm" | "md" | "lg"; className?: string; + unoptimize?: boolean; imageUrl: { - src?: string; + src: string; alt?: string; }; }) { return ( - + ); } diff --git a/apps/www/components/mdx-content.tsx b/apps/www/components/mdx-content.tsx index 39fc48926a..932323e041 100644 --- a/apps/www/components/mdx-content.tsx +++ b/apps/www/components/mdx-content.tsx @@ -6,8 +6,8 @@ import { BlogQuote } from "./blog/blog-quote"; import { Alert } from "./ui/alert/alert"; /** Custom components here!*/ export const MdxComponents = { - Image: (props: any) => , - img: (props: any) => , + Image: (props: any) => , + img: (props: any) => , Callout: Alert, th: (props: any) => ( diff --git a/apps/www/content/blog/cli-auth.mdx b/apps/www/content/blog/cli-auth.mdx index c9936b8826..99f29fb78e 100644 --- a/apps/www/content/blog/cli-auth.mdx +++ b/apps/www/content/blog/cli-auth.mdx @@ -4,7 +4,7 @@ title: Decoding CLI Authentication image: "/images/blog-images/covers/cli-auth.png" description: Command Line Interfaces (CLI) have become integral tools for developers looking to streamline their workflow, but how does it actually work? author: james -tags: ["engineering"] +tags: ["tutorials"] --- Command Line Interfaces (CLI) have become integral tools for developers looking to streamline their workflow. Some of the biggest names in tech, including Vercel, GitHub, Netlify, and Planetscale, offer CLIs that provide a powerful way to interact with their services. diff --git a/apps/www/content/blog/exploring-api-gateways.mdx b/apps/www/content/blog/exploring-api-gateways.mdx index e61c484903..19f5e1c2f3 100644 --- a/apps/www/content/blog/exploring-api-gateways.mdx +++ b/apps/www/content/blog/exploring-api-gateways.mdx @@ -2,9 +2,9 @@ date: 2024-05-23 title: Understanding API Gateways image: "/images/blog-images/covers/gatewayCover.png" -description: A beginners guide to API Gateways +description: A beginners guide to API Gateways author: michael -tags: ["engineering"] +tags: ["tutorials"] --- ## What Is an API Gateway? @@ -41,4 +41,4 @@ An API Gateway is like a gatekeeper for your microservices. Think of it as a gat ## Conclusion -In summary, API Gateways play a significant role in the realm of microservices. They facilitate communication, enhance security, and boost performance. Serving as a single point of entry, they manage authentication, balance load, monitor traffic, and oversee resource sharing. They also have the ability to direct requests, modify requests, group responses, and limit excessive usage. This makes them an integral component of any efficient system. However, it's crucial to prioritize security and performance. For developers, understanding these gateways and continuously striving to improve and secure our APIs is essential. \ No newline at end of file +In summary, API Gateways play a significant role in the realm of microservices. They facilitate communication, enhance security, and boost performance. Serving as a single point of entry, they manage authentication, balance load, monitor traffic, and oversee resource sharing. They also have the ability to direct requests, modify requests, group responses, and limit excessive usage. This makes them an integral component of any efficient system. However, it's crucial to prioritize security and performance. For developers, understanding these gateways and continuously striving to improve and secure our APIs is essential. diff --git a/apps/www/content/blog/ocr-service.mdx b/apps/www/content/blog/ocr-service.mdx index 218b226d39..9f7e1d1f1d 100644 --- a/apps/www/content/blog/ocr-service.mdx +++ b/apps/www/content/blog/ocr-service.mdx @@ -4,7 +4,7 @@ title: Building an OCR as a Service image: "/images/blog-images/covers/ocr-service.png" description: Learn how to use Unkey to make an Optical Character Recognition (OCR) API as a service author: wilfred -tags: ["engineering"] +tags: ["tutorials"] --- [Unkey](https://unkey.com) is an open-source API key management tool with real-time API key creation, updating, and verification. diff --git a/apps/www/content/blog/ratelimit-trpc-routes.mdx b/apps/www/content/blog/ratelimit-trpc-routes.mdx index a5a333a832..58067f43a5 100644 --- a/apps/www/content/blog/ratelimit-trpc-routes.mdx +++ b/apps/www/content/blog/ratelimit-trpc-routes.mdx @@ -4,7 +4,7 @@ title: How to ratelimit tRPC routes with Unkey description: Learn how to use Unkey to ratelimit tRPC routes in your Next.js application. author: james image: "/images/blog-images/covers/trpc-ratelimit.png" -tags: ["engineering"] +tags: ["tutorials"] --- Ratelimiting is not just a feature; it's a lifeline for production applications. Without it, you could face a skyrocketing bill. Your server could be pushed to its limits, leaving real users stranded and your application's reputation at stake. @@ -130,5 +130,3 @@ While this is a small overview of using Unkey's ratelimiting with tRPC, we also - Resources flagging You can read more about those features in our documentation on [Ratelimiting](https://www.unkey.com/docs/ratelimiting/introduction). - - diff --git a/apps/www/content/blog/ratelimiting-otp.mdx b/apps/www/content/blog/ratelimiting-otp.mdx new file mode 100644 index 0000000000..03a8604f99 --- /dev/null +++ b/apps/www/content/blog/ratelimiting-otp.mdx @@ -0,0 +1,206 @@ +--- +date: 2024-07-18 +title: Ratelimiting OTP endpoints +description: Without ratelimiting OTP endpoints you are exposed to brute force attacks, learn how to secure the endpoints using a ratelimiter. +author: james +image: "/images/blog-images/otp-ratelimit/otp-ratelimit.png" +tags: ["tutorials"] +--- + +## Understanding OTP +A One-Time Password (OTP) is a unique code valid for only one login session or transaction. It adds an extra layer of security by preventing fraudulent access to your accounts, even if someone else knows your password. +You've likely encountered OTPs many times. For instance, when logging into your bank account from a new device, you may receive an OTP via SMS or email, which you must enter to verify your identity. Another typical example is the login flow, where instead of entering a password, an OTP is sent to your email. + +Without ratelimiting, an attacker could try several OTPs in quick succession in a so-called 'brute force attack' to find the right one to gain access to an account. + +By limiting the number of OTP attempts within a specific timeframe, it becomes practically impossible for an attacker to guess the right OTP before it expires. + +## Implementing ratelimiting + +### Prerequisites + +- [Unkey account](https://app.unkey.com) +- Unkey root key with permissions `create_namespace`, `limit` + +If you prefer, you can use our example here and skip the entire tutorial below. Also, if you want to see it live, you can see an implementation below using Unkey and Resend [here](https://otp-example.vercel.app/) + +Before we begin with the tutorial, it should be stated that OTP implementations will have two separate requests: sending the OTP via email or SMS and verifying the request. + +Let’s start with the sending of an OTP. Below is an insecure OTP implementation with a fake email that sends a random 6-digit code to the user via a next.js server action. + +```jsx +"use server"; +import { randomInt } from "crypto"; + +export async function sendOTP(formData: FormData) { + try { + const email = formData.get("email") as string | null; + if (!email) { + return { + success: false, + error: "Email was not supplied, please try again", + statusCode: 400, + }; + } + const otp = randomInt(100000, 999999).toString(); + + const { data, error } = await emails.send({ + from: "james@unkey.com", + to: email, + subject: "OTP code", + text: `Your OTP code is ${otp}` + }); + // handled error + if (error) { + console.error(error); + return { success: false, error: "Failed to send email", statusCode: 500 }; + } + return { + success: true, + statusCode: 201, + }; + //catch + } catch (e) { + return { success: false, error: "Failed to send email", statusCode: 500 }; + } +} +``` + +### Adding ratelimiting to sending an OTP + +First, you’ll need to install the `@unkey/ratelimit` package to your project and then add the following imports. + +```jsx +import { Ratelimit } from "@unkey/ratelimit"; +import { headers } from "next/headers"; +``` + +We will use the headers to retrieve the IP of the requester and use that as an identifier to limit against. Now we need to configure the ratelimiter + +```jsx +const unkey = new Ratelimit({ + rootKey: process.env.UNKEY_ROOT_KEY, + namespace: "otp-send", + limit: 2, + duration: "60s", +}) + +export async function sendOTP(formData: FormData) { + // sending OTP logic +``` + +The above code will configure a new namespace named `otp-send` if it doesn’t exist and limit the requests to two per minute. Of course, any number of attempts, but two emails per minute should suffice for the end user. + +Now that we have our ratelimiter configured, we can modify the request to first retrieve the IP address; this will check for both the forwarded IP address and the real IP from the headers. We will use the forwarded IP first and fall back to the real IP. + +```jsx +export async function sendOTP(formData: FormData) { + try { + // check for forwarded + let forwardedIP = headers().get("x-forwarded-for"); + // check for real-ip + let realIP = headers().get("x-real-ip"); + if(forwardedIP){ + forwardedIP = forwardedIP.split(/, /)[0] + } + if (realIP) realIP = realIP.trim(); + // sending logic below +``` + +Now we have access to an identifier, and we can run our rate limit against it. Add the following code before checking if the user has provided an email. + +```jsx +const { success, reset } = await unkey.limit( + forwardedIP || realIP || "no-ip", + ); + const millis = reset - Date.now(); + const timeToReset = Math.floor(millis / 1000); + // if this is unsuccesful return a time to reset to the user so they know how long to wait + if (!success) { + return { + success: false, + error: `You can request a new code in ${timeToReset} seconds`, + statusCode: 429, + }; + } + + const email = formData.get("email") as string | null; + //shortened for tutorial. +``` + +You’ll notice that we check for `forwardedIP` and then the `realIP`, and finally, if nothing is available, we will use `no-ip` for the fallback. This endpoint is now protected; a user can send two requests per minute. Below is a demo of how you could present this to the user: + +Example of sending ratelimits + +### Ratelimiting the OTP verification + +The endpoint that verifies an OTP has more potential for brute force attacks; sending codes down with no restriction will give a bad actor plenty of time to try numerous codes to get the right one. + +This is where the flexibility of ratelimiting for Unkey can come into play while it is similar to the above server action. For example + +```tsx +export async function verifyOTP(prevState: any, formData: FormData) { + try { + // check for forwarded + let forwardedIP = headers().get("x-forwarded-for"); + // check for real-ip + let realIP = headers().get("x-real-ip"); + if (forwardedIP) { + forwardedIP.split(/, /)[0]; + } + if (realIP) { + realIP = realIP.trim(); + } + + const code = formData.get("code") as string | null; + + if (!code) { + return { + success: false, + error: "Code was not supplied, please try again", + statusCode: 400, + }; + } + + const { success, reset } = await unkey.limit( + forwardedIP || realIP || "no-ip", + ); + const millis = reset - Date.now(); + const timeToReset = Math.floor(millis / 1000); + + if (!success) { + return { + success: false, + error: `You have been rate limited, please wait ${timeToReset} seconds and try entering a new code`, + statusCode: 429, + }; + } + // Handle verification of your OTP +``` + +You can set the limits and namespace to be different, allowing you to be more restrictive and keep all your analytical data separated, for example. + +```jsx +const unkey = new Ratelimit({ + rootKey: process.env.UNKEY_ROOT_KEY!, + namespace: "otp-verify", + limit: 2, + duration: "30s", +}); +``` + +This operation will allow a user to try twice every 30 seconds before it ratelimits the operation for the IP. Below is an example of how this could look in your application from the example code. + +Example of verifying ratelimits + +## Best Practices in Rate Limiting OTP + +Implementing rate limiting is one thing, but ratelimiting effectively requires following best practices. Here are some tips: + +- **Set reasonable limits**: Your users should have enough attempts to enter their OTP correctly, but not so many that an attacker could guess. +- **Educate your users**: Make sure your users understand why they're being blocked from logging in after too many attempts and how long they have to wait before they can try again. +- **Monitor and adjust**: Regularly review your system's performance and adapt your limits as needed. + +These practices enhance the security and efficiency of OTPs while maintaining a positive user experience. + +You can read more about Unkey’s Ratelimiting our [documentation](https://www.unkey.com/docs/ratelimiting/introduction), you can see the [demo](https://otp-example.vercel.app/) of this in action and test what happens when you go over limits. diff --git a/apps/www/content/blog/secure-supabase-functions-using-unkey.mdx b/apps/www/content/blog/secure-supabase-functions-using-unkey.mdx index b56b81bac2..5f3bdb1db5 100644 --- a/apps/www/content/blog/secure-supabase-functions-using-unkey.mdx +++ b/apps/www/content/blog/secure-supabase-functions-using-unkey.mdx @@ -4,7 +4,7 @@ title: "Secure your Supabase functions with Unkey" image: "/images/blog-images/covers/unkey-supabase.png" description: "Learn how to use Unkey to secure your Supabase functions" author: james -tags: ["product"] +tags: ["tutorials"] --- Supabase offers [edge functions](https://supabase.com/docs/guides/functions) built upon Deno. They have a variety of uses for applications like OpenAI or working with their storage product. In this post, we will show you how to use Unkey to secure your function in just a few lines of code. diff --git a/apps/www/content/blog/using-unkey-with-auth.mdx b/apps/www/content/blog/using-unkey-with-auth.mdx index cf38d78dd7..60a43686ad 100644 --- a/apps/www/content/blog/using-unkey-with-auth.mdx +++ b/apps/www/content/blog/using-unkey-with-auth.mdx @@ -4,7 +4,7 @@ title: "Using Unkey with your Authentication Provider" image: "/images/blog-images/covers/unkey-auth-provider.png" description: "Learn how to use Unkey with an auth provider, to associate your keys with your users." author: james -tags: ["engineering"] +tags: ["tutorials"] --- When working with your external facing API and having a client application, you need to identify which user owns an API Key. Having a way to identify the user allows you to understand their usage of your product better. This blog is going to cover how you can use your authentication provider to add a way to identify the user. diff --git a/apps/www/public/images/blog-images/otp-ratelimit/15fps_1080.gif b/apps/www/public/images/blog-images/otp-ratelimit/15fps_1080.gif new file mode 100644 index 0000000000..743d915f2a Binary files /dev/null and b/apps/www/public/images/blog-images/otp-ratelimit/15fps_1080.gif differ diff --git a/apps/www/public/images/blog-images/otp-ratelimit/otp-ratelimit.png b/apps/www/public/images/blog-images/otp-ratelimit/otp-ratelimit.png new file mode 100644 index 0000000000..238b27cd89 Binary files /dev/null and b/apps/www/public/images/blog-images/otp-ratelimit/otp-ratelimit.png differ diff --git a/apps/www/public/images/blog-images/otp-ratelimit/otp-verify-1080.gif b/apps/www/public/images/blog-images/otp-ratelimit/otp-verify-1080.gif new file mode 100644 index 0000000000..ac732aa08c Binary files /dev/null and b/apps/www/public/images/blog-images/otp-ratelimit/otp-verify-1080.gif differ