diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-add-js-code.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-add-js-code.png new file mode 100644 index 00000000000..17fa0b49f0d Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-add-js-code.png differ diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-add-route.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-add-route.png new file mode 100644 index 00000000000..17ba625a574 Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-add-route.png differ diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-create-worker.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-create-worker.png new file mode 100644 index 00000000000..5280932c06d Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-create-worker.png differ diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-edit-js-code.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-edit-js-code.png new file mode 100644 index 00000000000..945093020e9 Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-edit-js-code.png differ diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-pages-overview.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-pages-overview.png new file mode 100644 index 00000000000..24ce7bbee26 Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-pages-overview.png differ diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-preview-result.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-preview-result.png new file mode 100644 index 00000000000..b156d7f8839 Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-preview-result.png differ diff --git a/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-preview-worker.png b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-preview-worker.png new file mode 100644 index 00000000000..adc4512a760 Binary files /dev/null and b/astro/public/img/docs/extend/examples/api-gateways/cloudflare-workers-preview-worker.png differ diff --git a/astro/src/content/docs/extend/examples/api-gateways/cloudflare-api-gateway.mdx b/astro/src/content/docs/extend/examples/api-gateways/cloudflare-api-gateway.mdx new file mode 100644 index 00000000000..6899da6f07d --- /dev/null +++ b/astro/src/content/docs/extend/examples/api-gateways/cloudflare-api-gateway.mdx @@ -0,0 +1,391 @@ +--- +title: Cloudflare Workers Functions (API Gateway) +description: Learn about the Cloudflare Workers and JWT integration. +navcategory: developer +section: extend +subcategory: examples +tertcategory: api gateways +--- +import InlineField from 'src/components/InlineField.astro'; +import InlineUIElement from 'src/components/InlineUIElement.astro'; +import ScrollRef from 'src/components/ScrollRef.astro'; +import FrameworkIntegrationTutorialList from 'src/content/docs/_shared/_framework-integration-tutorial-list.mdx'; + +## Overview + +Cloudflare is a global cloud platform designed to make everything you connect to the Internet secure, private, fast, and reliable. Creating an 'API Gateway' app on Cloudflare is a +great way to manage, secure, and optimize your APIs. Cloudflare offers several services that can help you achieve this, such as Cloudflare Workers, Cloudflare Gateway, and Cloudflare Load Balancing. +This step-by-step guide will help you set up an Cloudflare 'API Gateway' similar to API Gateway's on other providers, on Cloudflare. + +You can set up Cloudflare workers to handle authentication and authorization to a resource through JSON Web Tokens (JWTs) issued to a user by an identity provider. + +In this document, you'll learn how to set up Cloudflare workers and FusionAuth as the identity provider to protect a worker function running on your Cloudflare. + +Please note, there are many ways to accomplish this task, for this guide we chose to use a worker function and create it on the portal Cloudflare. This can also be done using the +wrangler CLI tools, but this is left as a exercise for the reader. More info in wrangler CLI can be found [here](https://developers.cloudflare.com/workers/wrangler/). + +## Prerequisites + +* A FusionAuth instance running on a publicly accessible URL. You can spin up a [basic FusionAuth Cloud instance](/pricing) or [install it on any server](/docs/get-started/download-and-install). If you are not able to host FusionAuth on a public server, you can tunnel your local instance of FusionAuth through a tool like [ngrok](https://ngrok.com). +* An [Cloudflare account](https://dash.cloudflare.com/login). + +### Exposing FusionAuth Publicly + +If your FusionAuth instance is already publicly available, you can skip this section. + +If you are hosting FusionAuth locally and want to expose it publicly using ngrok, you can run the following command: + +```shell title="Exposing a local FusionAuth instance through ngrok" +ngrok http 9011 +``` + +This assumes your local FusionAuth instance is running on port 9011, which is the default for local FusionAuth installations. + +Look for the `Forwarding` line in the output. It should look something like this: + +```shell title="Output forwarding address from ngrok" +Forwarding https://7817-102-218-66-2.eu.ngrok.io -> http://localhost:9011 +``` + +The forwarding address will be publicly accessible and you can use this to replace `` in this tutorial. + +## Set Up FusionAuth + +Navigate to your FusionAuth instance. + +First, you need to make sure the JWT issuer setting is correct. Navigate to Tenants -> Your Tenant and change the issuer to the URL of your FusionAuth instance. For example, `https://local.fusionauth.io`. Record this value, as we'll set it in the Cloudflare worker Authorizer later. + +Next, you need to configure an application that will issue tokens to access the Cloudflare project. + +Navigate to Applications and create a new application. Fill out the Name field, then click the OAuth tab. + +Make sure that the Enabled grants checkboxes have the `Authorization Code` and `Refresh Token` grants enabled. + +Your application should look like this. + +The FusionAuth example configuration + +On the JWT tab, toggle the Enabled field and set the Access Token signing key and Id Token signing key to `Auto generate a new key on save...` + +The JWT configuration + +Click the Save button. + +Edit the new application. You should see a value in the Client Id field. Copy it and put it in a text file. You'll use it in the step. + +Extracting the Client Id and secret + + +## Set Up Cloudflare + +Navigate to your [Cloudflare dashboard](https://dash.cloudflare.com/login), and login or create an [Cloudflare account](https://developers.cloudflare.com/fundamentals/setup/account/create-account/) if you haven't got one already. + + +## Create a Worker Function to Secure a cloudflare worker endpoint (API) +In the left hand side toolbar navigate to the Workers & Pages section and click on overview. + +The Cloudflare Workers & Pages overview + +Click on the `Create Worker` button. + +Give a descriptive name to the worker, we will use `privatefunction` (take note of the name as you will need it again later), click on the deploy button at the bottom. +For now the default script is fine, we will update it in the next step. + +Create a cloudflare worker + +We can test that our privateFunction is working by clicking on the preview link provided on the next page. If successful, it will open a new browser tab with 'Hello world in'. + +Preview a cloudflare worker + +You should see something similar to the screen below. + +Cloudflare worker preview result +When done close the browser tab and go back to the worker window. + +Click on the `Edit Code` button. To replace the `hello world` code with the sample code below to handle our authorization requests to FusionAuth: + +Edit cloudflare worker js + +To replace the existing code of the worker: + - Click on the code windows + - Select everything and delete it + - Copy and paste the new code below in to the window + - Click on the deploy button on the top left + - Click on Save and deploy + +Note: Remember to update your fusionAuthApiKey ,fusionAuthClientId and fusionAuthUrl + +Replace cloudflare worker js + +```javascript title="Worker JS to handle claims requests, and validate the token" + + // Replace with your FusionAuth API key + const fusionAuthApiKey = ''; + const fusionAuthClientId = 'your fusionAuthClientId'; + addEventListener('fetch', event => { + event.respondWith(handleRequest(event.request)) + }) + + async function handleRequest(request) { + const { pathname } = new URL(request.url) + + // Check if the request is for the /api/claims endpoint + if (pathname === '/api/claims') { + // Verify the authentication token + const authToken = request.headers.get('Authorization') + const verificationResponse = await verifyAuthToken(authToken) + console.log("ver:",verificationResponse) + + if (verificationResponse.active === true) { + // Extract the claims from the verification response + const claims = verificationResponse + console.log(verificationResponse) + + // Call your Worker API function and pass the claims + const response = await handleClaimsAPI(claims) + return response + } else { + return new Response('Unauthorized', { status: 401 }) + } + } else { + return new Response('Not found', { status: 404 }) + } + } + + async function verifyAuthToken(authToken) { + const url = 'https:///oauth2/introspect' + const options = { + method: 'POST', + headers: { + 'Authorization': `Bearer ${fusionAuthApiKey}`, + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: + `token=${authToken}&client_id=${fusionAuthClientId}` + } + + // console.log('Request options:', options) + + const response = await fetch(url, options) + const clonedResponse = response.clone(); + const responseText = await response.text() + console.log('Response status:', response.status) + console.log('Response text:', responseText) + + // Check if the response is JSON + try { + return await clonedResponse.json(); + } catch (error) { + console.error('Error parsing JSON response:', error) + return { error: 'Invalid JSON response' } + } + } + + async function handleClaimsAPI(claims) { + // Your Worker API function logic + const response = { + statusCode: 200, + body: JSON.stringify(claims) + } + console.log("claims:",claims) + return new Response(response.body, { status: response.statusCode }) + } + +``` + +### Code explanation + +This Cloudflare Worker code sets up an API gateway that integrates with FusionAuth for authentication and authorization. +Here's a breakdown of what the code does: + +Configuration: +- The code starts by defining two constants: fusionAuthApiKey and fusionAuthClientId, which are used for authentication with the FusionAuth API. +- Replace this with your keys as you set it up in FusionAuth. + +Event Listener: + +- The addEventListener function is used to listen for incoming requests and pass them to the handleRequest function. + +HandleRequest Function: + +This function is the main entry point for handling incoming requests. +- It checks if the request path is /api/claims. (Other endpoints can be added here using the same technique) +- If the path matches, it extracts the authentication token from the request headers. +- It calls the verifyAuthToken function to verify the authentication token with the FusionAuth API. +- If the token is valid (verificationResponse.active === true), it extracts the claims from the verification response and calls the handleClaimsAPI function, passing the claims as an argument. +- If the token is invalid, it returns an "Unauthorized" response with a 401 status code. +- If the request path doesn't match /api/claims, it returns a "Not found" response with a 404 status code. + +Verify Auth Token Function: + +This function is responsible for verifying the authentication token with the FusionAuth API. +- It constructs the request options, including the FusionAuth API key, client ID, and the authentication token. +- It sends a POST request to the FusionAuth introspection endpoint (/oauth2/introspect) with the request options. Replace your public fusionAuth url here. +- If the response is valid JSON, it returns the parsed JSON data. +- If the response is not valid JSON, it returns an error object. + +HandleClaimsAPI Function: + +This function represents the logic for your API endpoint. +- It receives the claims from the verified authentication token. +- In this example, it simply constructs a response object with a 200 status code and the claims as the response body. +- It returns a new Response object with the response body and status code. + +In summary, this Cloudflare Worker sets up an API gateway that accepts requests to the /api/claims endpoint. It verifies the authentication token using the FusionAuth API and, if valid, passes the claims to the handleClaimsAPI function, which represents the logic for your API endpoint. The code handles responses, error cases, and logging for debugging purposes. + +After deployment, a new route will be available with the same name as the `privateFunction` as we chose when creating the worker above. + +Your full URL to the claims endpoint should look similar to this: `https://privatefunction..workers.dev/api/claims`. + +If you call this route through a browser, Postman, or curl, you will receive a `401` HTTP status code and an `Unauthorized` message. We'll create a token next with FusionAuth to show how the route can be successfully called. + +Call the API gateway with the following curl command to test: + +```shell title="Calling the API Gateway endpoint " +curl --location --request GET '' + +``` + +Where: + +* `` is the invoke URL saved earlier. + +You should see the function return: + +```shell title="Return token from the API call" + Unauthorized +``` + +## Testing With a Token From FusionAuth + +To access the secured worker function, we'll need to get a valid token. Normally, your frontend app would use the Authorization Code grant which would redirect a user to FusionAuth to login, obtain a code, and then exchange that code for a token, using a framework of your choice. [More on the Authorization Code grant.](/docs/lifecycle/authenticate-users/oauth/) + +For the purposes of this guide, we'll call the [FusionAuth Login API](/docs/apis/login) on behalf of a user to create a token directly. This is a more straightforward means of getting a token, but the generated token will be similar with either process. + +To use the Login API, there are a few settings that need to be changed on FusionAuth. In the side panel in FusionAuth, select Settings > API Keys. Click the green + button at the top left to create a new key. + +Give the key a meaningful description, like `API Gateway Key`. Select the Tenant that you created the FusionAuth application under (normally `Default`). + +Then, under Endpoints, allow `POST` on the `/api/login` route. This key will only be allowed to call this endpoint with the `POST` method. + +Enabling the login API + +Save the API key. You should now see a list of all API keys. Click on the lock icon next to the key field for the key you just created and copy the API key for later use. Make sure you get the API key value and not the API key Id. + +Enabling the login API + +To test, you'll need to link a FusionAuth user to your application. You can either use the existing admin user you use to log into FusionAuth or create a new isolated user. + +To link your existing user, navigate to the user list through the Users menu item in the sidebar. Find your user and click the Manage action button on the user. Click the Registrations tab. Click Add registrations and select the FusionAuth application you set up earlier. + +Alternatively, to create a new user, navigate to Users in the sidebar and click the green + button at the top right of the screen to create a new user. Choose the appropriate tenant (usually `default`) and fill in the information. You can choose not to send an email to set up the password but rather set it directly when creating the user by toggling the Send email to set up password switch. Record this user email and password, as we'll use it to obtain a token. + +After saving the user, click the Registrations tab. Click Add registrations and select the FusionAuth application you set up earlier. + +The application registrations tab + +Using curl or Postman, make a POST request to your FusionAuth instance's `/api/login` endpoint. This will return a JWT in the response. + +The curl call should look like this: + +```shell title="curl command to obtain a JWT token" +curl --location --request POST 'https:///api/login' \ +--header 'Authorization: ' \ +--header 'Content-Type: application/json' \ +--data-raw ' { + "loginId": "", + "password": "", + "applicationId": "", + "noJWT" : false + }' +``` + +Where: + +* `` is the URL of your FusionAuth installation. +* `` is the API key created above, enabled for login. +* `` is the email address of the user added above. +* `` is the password of the user added above. +* `` is the Id of the FusionAuth application registered for the user. + +The return response from FusionAuth should look similar to the following: + +```json title="Login API Response" +{ + "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImhDUjA4X3daR2s0OUFlYUFmRDY5ZmJKWmRGTSJ9.eyJhdWQiOiI2M2I3M2Y3Ni03NDAwLTQ4N2QtYjEyMi01NzA1Yjg0OGRhODAiLCJleHAiOjE2NzMzNjYyMDQsImlhdCI6MTY3MzM2MjYwNCwiaXNzIjoiaHR0cHM6Ly9mdXNpb25hdXRoLnJpdHphLmNvIiwic3ViIjoiMzk2MzAwMGYtNjg2ZC00MTY5LWI2MjgtOWM5YzQ1MzRiNzgwIiwianRpIjoiZDk3ZGIyZWYtZjExNS00ZDIxLWFlOTQtMDIyN2RmMGU4YzI5IiwiYXV0aGVudGljYXRpb25UeXBlIjoiUEFTU1dPUkQiLCJlbWFpbCI6ImJvYkBhd3MuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInByZWZlcnJlZF91c2VybmFtZSI6ImJvYmF3cyIsImFwcGxpY2F0aW9uSWQiOiI2M2I3M2Y3Ni03NDAwLTQ4N2QtYjEyMi01NzA1Yjg0OGRhODAiLCJyb2xlcyI6W10sImF1dGhfdGltZSI6MTY3MzM2MjYwNCwidGlkIjoiZjAwNGMxZmUtNDg0Yi05MDJjLWQ3Y2EtYmRiYzQ2NGRhMGI3In0.m7gzXhNLToPNVE1p5Vo2pLgP6WBcPNfS_zZJnJ81mdEgi6-orViz-tU8j0L8wva0-8KlMdy54cq_XjnDnYJ0aX90O4ZE_QVU5NuDDfzXH14wQtKQoIIydsB6ZvQoBt8JNFUHJb9ANLCGnfn6FVQKqPIzye18Gx_7wYSVokw3eLNFyzrq9dwOD5Q8V9gvZmXV2pTokQAtA7qFaadb2dIeFlSEB7wamKiZLXILjeWAeMbbvAAMQZWFh46UJjwr06QTd8PxQmRwDWWznJy1Vs8EAgZA4vkRSWnn3IbiaCtOaL1ANuEex6il7q32ahxj0Ncm9wn0DbDsQE9NB0CCNTSIhA", + "tokenExpirationInstant": 1673366204805, + "user": { + "sampleuserdata" : "..." + } +} +``` + +Copy the `token` value. This is a JWT, which we can use to access the API gateway function. + +Now call the API gateway with the following curl command: + +```shell title="Calling the API Gateway endpoint with the JWT" +curl --location --request GET 'https:///privateFunction/api/claims' \ +--header 'Authorization: Bearer ' +``` + +Where: + +* `` is the invoke URL saved earlier. +* `` is the JWT from the `/api/login` call. + +You should see the function return with a reflection of the claims it received: + +```json title="Return token from the API call" +{ + "active":true, + "applicationId":"18740210-b386-4679-a626-a84d9abdb8dd", + "aud":"18740210-b386-4679-a626-a84d9abdb8dd", + "auth_time":1716133402, + "authenticationType": "PASSWORD", + "email":"apiuser@email.com", + "email_verified":true, + "exp":1716137002, + "iat":1716133402, + "iss":"https://fusionauth", + "jti":"64d7e970-d0ff-4bbc-82b4-adc80413818e", + "preferred_username":"apiUser", + "roles":[], + "sub":"588155c8-b1df-4fb6-8c40-5f431e770644", + "tid":"31f7e6b3-9c71-4a7f-b1e2-45be0d7067d8"} +``` + +## Troubleshooting + +* If you are running a local instance of FusionAuth, Cloudflare worker will not be able to reach the key sets needed to validate the JWT. You will either need to host FusionAuth on a publicly accessible URL or proxy your local instance through a tool like [ngrok](https://ngrok.com). + +* Ensure that your FusionAuth application's Access Token signing key and Id Token signing key on the application's JWT tab are asymmetric (RS256). + +* The JWT issued by the [FusionAuth Login API](/docs/apis/login) has an expiry. If you wait too long before using it to call the Cloudflare worker, the token will expire and the call will fail. You can resolve this by re-running the curl command to obtain a JWT token and using the new token. + +## Next Steps + +You can build a complete HTTP API using Cloudflare Workers, Cloudflare Gateway, and Cloudflare Load Balancing, and FusionAuth without managing any servers. + +#### Configure Routes +In the Workers section, you can set up routes to associate your Worker with your domain or subdomains. For example, you can route https://api.yourdomain.com/* to your Worker. +### Custom Domain +If you want to use a custom domain for your API (e.g., api.yourdomain.com), set up a DNS record in the Cloudflare dashboard pointing your subdomain to your-workers-subdomain.workers.dev. + +Both the above options can be configured on the trigger section below. This is only available if you have a domain setup in the cloudflare environment. Take not that it can take some time to propagate. + + + +#### Secure Your API with Cloudflare Gateway. +- Access Cloudflare Zero Trust +- Go to the Zero Trust dashboard in Cloudflare (formerly known as Access) to set up security policies for your API. + +Create Gateway Policies: +- Define policies to restrict access to your API, such as IP filtering, token-based access, or integrating with certain identity providers for alternate web login methods. FusionAuth is not an option here but it can +manually be added as a login provide here using OpenID connect as in alternate solution. + +Finally, you can configure FusionAuth to ensure that the user is registered for the Cloudflare application or [fire off webhooks](/docs/extend/events-and-webhooks/) when the user logs in. + +You used the FusionAuth API to create a test token on behalf of a user. For a production system, the token will be generated after a user signs in to your application through a frontend. Check out some of the framework integration tutorials to implement that: + + \ No newline at end of file