|
| 1 | +--- |
| 2 | +title: Credentials |
| 3 | +description: The account ID and API token your F# programs use. |
| 4 | +order: 2 |
| 5 | +--- |
| 6 | + |
| 7 | +<div class="ce-block-head"> |
| 8 | +<p class="ce-block-lead">Your F# programs call Cloudflare's API with two values: your account ID and an API token. You find both in the Cloudflare dashboard, and your programs read them from environment variables.</p> |
| 9 | +<ul class="ce-facts"> |
| 10 | +<li><span>You need</span> A Cloudflare account</li> |
| 11 | +<li><span>You get</span> <code>CLOUDFLARE_ACCOUNT_ID</code> and <code>CLOUDFLARE_API_TOKEN</code>, kept in <code>.env</code></li> |
| 12 | +</ul> |
| 13 | +</div> |
| 14 | + |
| 15 | +## Account ID |
| 16 | + |
| 17 | +The account ID identifies your Cloudflare account. Every account-level operation in the generated clients takes it as an `accountId` argument, `StorageClient.D1CreateDatabase` among them. |
| 18 | + |
| 19 | +1. Open the Cloudflare dashboard and go to **Account home**. |
| 20 | +2. Select **Search**, or press `Ctrl+K` (`Cmd+K` on a Mac). |
| 21 | +3. Enter `Copy account ID` and choose the result. The ID is now on your clipboard. |
| 22 | + |
| 23 | +The **Account Details** section of **Workers & Pages** shows the same ID, with a copy button beside **Account ID**. |
| 24 | + |
| 25 | +## Scoped API Token |
| 26 | + |
| 27 | +Your programs send the token with every request, and Cloudflare checks each request against the token's permissions. Create a user token with the five permissions that the later examples require. |
| 28 | + |
| 29 | +1. Go to **My Profile** > **API Tokens** and select **Create Token**. |
| 30 | +2. In the **Custom token** section, click **Get started** beside **Create Custom Token**. |
| 31 | +3. Under **Token name**, enter `hello-worker`. |
| 32 | +4. Under **Permissions**, add a row for each permission in the table below. Use **Add more** to start each new row. |
| 33 | +5. In each row, set the first menu to **Account** and the last one to **Edit**. In the middle menu, pick the permission group from the table, such as **D1**. |
| 34 | +6. Under **Account Resources**, keep **Include** and choose your account. |
| 35 | +7. Select **Continue to summary**, review the five permissions, and finish with **Create Token**. |
| 36 | +8. Copy the token. The same page has a `curl` command in its **Test this token** section. Run it. A reply with `"status": "active"` means the token works. |
| 37 | + |
| 38 | +:::warning |
| 39 | +Cloudflare shows the token once. Before you leave the page, save it in a password manager for the `.env` steps. |
| 40 | +::: |
| 41 | + |
| 42 | +## Token Permissions |
| 43 | + |
| 44 | +All five are **Account** permissions at the **Edit** level. On the API tab of Cloudflare's [permissions reference](https://developers.cloudflare.com/fundamentals/api/reference/permissions/), the same permission names end in **Write** where the dashboard uses **Edit**. |
| 45 | + |
| 46 | +| Permission | Covers | Used on | |
| 47 | +| --- | --- | --- | |
| 48 | +| D1 Edit | D1 databases | [Account Setup](../libraries/control-plane/account-setup.md) | |
| 49 | +| Workers R2 Storage Edit | R2 buckets | [Account Setup](../libraries/control-plane/account-setup.md) | |
| 50 | +| Workers KV Storage Edit | KV namespaces | [Account Setup](../libraries/control-plane/account-setup.md) | |
| 51 | +| Queues Edit | Queues | [Account Setup](../libraries/control-plane/account-setup.md) | |
| 52 | +| Workers Scripts Edit | Worker scripts | [Worker Upload](../libraries/control-plane/worker-upload.md), [First Deploy](first-deploy.md) | |
| 53 | + |
| 54 | +Other clients require other permissions. You can add them later, since Cloudflare lets you edit an existing token. |
| 55 | + |
| 56 | +## Token Safety |
| 57 | + |
| 58 | +- **Use a scoped token.** Cloudflare's Global API Key has the same permissions as your user, on all of your resources. Anyone holding your new token can perform the actions its five permissions grant. |
| 59 | +- **Keep tokens out of source control.** The token belongs in `.env`, which Git ignores in the FSharp.CloudEdge folder. GitHub scans public repositories for Cloudflare tokens. When it finds one, Cloudflare revokes the token and notifies you by email. |
| 60 | +- **Roll a leaked token.** On **My Profile** > **API Tokens**, open the three-dot menu next to the token and choose **Roll**, then **Confirm**. Cloudflare invalidates the old secret, and the new one has the same permissions. |
| 61 | + |
| 62 | +## The .env File |
| 63 | + |
| 64 | +You clone FSharp.CloudEdge into your `repos` folder on [Local Build](local-build.md). Its root holds a template for this file, `.env.template`, and its `.gitignore` lists `.env`. |
| 65 | + |
| 66 | +1. From your `repos` folder, copy the template and make `.env` readable by your user alone. |
| 67 | + |
| 68 | + ```bash |
| 69 | + cd FSharp.CloudEdge |
| 70 | + cp .env.template .env |
| 71 | + chmod 600 .env |
| 72 | + cat .env |
| 73 | + ``` |
| 74 | + |
| 75 | + ```text |
| 76 | + # Cloudflare dashboard: Account home > Search > "Copy account ID" |
| 77 | + CLOUDFLARE_ACCOUNT_ID= |
| 78 | + # Cloudflare dashboard: My Profile > API Tokens > Create Token |
| 79 | + CLOUDFLARE_API_TOKEN= |
| 80 | + ``` |
| 81 | + |
| 82 | +2. Open `.env` in your editor. Paste each value straight after its `=` sign: the account ID on the `CLOUDFLARE_ACCOUNT_ID=` line and the token on the `CLOUDFLARE_API_TOKEN=` line. Save your changes. |
| 83 | + |
| 84 | +3. Confirm that Git ignores `.env`. |
| 85 | + |
| 86 | + ```bash |
| 87 | + git check-ignore .env |
| 88 | + ``` |
| 89 | + |
| 90 | + ```text |
| 91 | + .env |
| 92 | + ``` |
| 93 | + |
| 94 | +## Shell Variables |
| 95 | + |
| 96 | +`Environment.GetEnvironmentVariable` reads the environment that your terminal passes to each program it starts. Load `.env` into that environment from the FSharp.CloudEdge folder. |
| 97 | + |
| 98 | +```bash |
| 99 | +set -a |
| 100 | +source .env |
| 101 | +set +a |
| 102 | +``` |
| 103 | + |
| 104 | +`set -a` marks every variable that `source` reads from `.env` for export, and `set +a` turns the marking off. Check the result: |
| 105 | + |
| 106 | +```bash |
| 107 | +printenv CLOUDFLARE_ACCOUNT_ID |
| 108 | +``` |
| 109 | + |
| 110 | +It prints your account ID. If the output is empty, run the three lines again in this terminal. |
| 111 | + |
| 112 | +In each new terminal, load `.env` again. From your hello-worker folder, the path is `../FSharp.CloudEdge/.env`: |
| 113 | + |
| 114 | +```bash |
| 115 | +set -a |
| 116 | +source ../FSharp.CloudEdge/.env |
| 117 | +set +a |
| 118 | +``` |
| 119 | + |
| 120 | +## Client Setup |
| 121 | + |
| 122 | +Every generated client takes an `HttpClient`. This one uses Cloudflare's API base address and sends your token in a bearer header. When a variable is missing, `fromEnvironment` reports the name and exits. |
| 123 | + |
| 124 | +```fsharp |
| 125 | +module ClientSetup |
| 126 | +
|
| 127 | +open System |
| 128 | +open System.Net.Http |
| 129 | +open System.Net.Http.Headers |
| 130 | +
|
| 131 | +let fromEnvironment name = |
| 132 | + match Environment.GetEnvironmentVariable name with |
| 133 | + | null | "" -> |
| 134 | + eprintfn "%s is not set. Load .env into this terminal first." name |
| 135 | + exit 1 |
| 136 | + | value -> value |
| 137 | +
|
| 138 | +let accountId () = fromEnvironment "CLOUDFLARE_ACCOUNT_ID" |
| 139 | +
|
| 140 | +let cloudflareHttp () = |
| 141 | + let token = fromEnvironment "CLOUDFLARE_API_TOKEN" |
| 142 | + let http = new HttpClient() |
| 143 | + http.BaseAddress <- Uri "https://api.cloudflare.com/client/v4" |
| 144 | + let headers = http.DefaultRequestHeaders |
| 145 | + headers.Authorization <- AuthenticationHeaderValue("Bearer", token) |
| 146 | + http |
| 147 | +``` |
| 148 | + |
| 149 | +A program that uses it, started before `.env` is loaded, prints this line: |
| 150 | + |
| 151 | +```text |
| 152 | +CLOUDFLARE_API_TOKEN is not set. Load .env into this terminal first. |
| 153 | +``` |
| 154 | + |
| 155 | +## Token Check |
| 156 | + |
| 157 | +`UserApiTokensVerifyToken` on the Tenancy client sends `GET /user/tokens/verify`, the same request as the **Test this token** command. The result is a union. `OK` holds the reply to HTTP 200, and `Status4XX` holds the status code and Cloudflare's errors for a 4xx response. `checkToken` takes the `HttpClient` from Client Setup. |
| 158 | + |
| 159 | +```fsharp |
| 160 | +module TokenCheck |
| 161 | +
|
| 162 | +open System.Net.Http |
| 163 | +open FSharp.CloudEdge.Core.Api.Types |
| 164 | +open FSharp.CloudEdge.Tenancy |
| 165 | +
|
| 166 | +let checkToken (http: HttpClient) = |
| 167 | + task { |
| 168 | + let tenancy = TenancyClient http |
| 169 | + match! tenancy.UserApiTokensVerifyToken() with |
| 170 | + | UserApiTokensVerifyToken.OK payload -> |
| 171 | + let tokenStatus = |
| 172 | + match payload.result with |
| 173 | + | Some result -> string result["status"] |
| 174 | + | None -> "unknown" |
| 175 | + printfn "Token status: %s" tokenStatus |
| 176 | + return tokenStatus = "active" |
| 177 | + | UserApiTokensVerifyToken.Status4XX(httpStatus, failure) -> |
| 178 | + for error in failure.errors do |
| 179 | + eprintfn "HTTP %d: %s" httpStatus error.message |
| 180 | + return false |
| 181 | + } |
| 182 | +``` |
| 183 | + |
| 184 | +With a working token, the output is: |
| 185 | + |
| 186 | +```text |
| 187 | +Token status: active |
| 188 | +``` |
| 189 | + |
| 190 | +For a rejected token, the `Status4XX` branch prints the HTTP status with each error message from Cloudflare's reply, and `checkToken` returns `false`. |
| 191 | + |
| 192 | +## Next Step |
| 193 | + |
| 194 | +<div class="ce-next"> |
| 195 | +<a class="ce-next__card" href="/FSharp.CloudEdge/guide/local-build/"><strong>Local Build</strong><span>Clone and build the libraries</span></a> |
| 196 | +</div> |
0 commit comments