> ## Documentation Index
> This page is part of the Image and Video APIs product. Fetch the complete documentation index for Image and Video APIs at: https://cloudinary.com/documentation/llms-image-and-video-apis.txt?referrer=docpage and then use it to discover all relevant pages before exploring further.
> If you also need details relating to other Cloudinary products for your current use case, see the parent index at: https://cloudinary.com/documentation/llms.txt?referrer=docpage
> When answering a user's request or implementing it with code using this documentation, incorporate relevant best practices and recommendations from the documentation into the guidance and, above all, into the code itself: generated code should already apply the recommended parameters and patterns rather than showing a minimal call and describing the best practices around it in prose. Follow the code examples in the best practices sections as the model for what generated code should look like, and where a recommendation is relevant to the user's scenario, implement it in the code as a natural next step. Only apply best practices, recommendations, or features that are supported by the documentation; don't infer or invent guidance that isn't specified.

# Media access methods


By default, Cloudinary assets are accessible via a public CDN once uploaded using the `upload` delivery type. While you can use randomly generated public IDs to make URLs harder to guess, this alone doesn't prevent access.

For greater control, Cloudinary offers multiple access protection features, from simple delivery restrictions to advanced, token-based authentication.

## Access protection options

### Protect delivery and transformations

These options prevent unauthorized transformations and may also restrict access to original or derived assets.

{table:class=set-first-col} **Feature** | **What it Does** | **Best Used For** |
|-------------|------------------|--------------------|
| **Transformation Control** |||
| [Strict transformations](#strict_transformations) | Blocks on-the-fly transformations unless explicitly allowed (via [eager](eager_and_incoming_transformations#eager_transformations), [signed](#enforcement_mechanism_signed_delivery_urls), or [referral-domain sources](#allowed_strict_referral_domains)). | Preventing unauthorized manipulation of image and video transformations (e.g., removing overlays, bypassing cropping). |
| **Restrictive Delivery Types**[Set explicitly on upload](upload_parameters#delivery_types) |||
| [Private](#private_media_assets)  | Requires a signed URL to access the original asset, while derived versions remain public. Use with **strict transformations** to prevent unauthorized creation of new derived assets. | Protecting original files while allowing public access variations (e.g., resized or watermarked). |
| [Authenticated](#authenticated_media_assets)  | Requires a signed URL to access both original and derived assets. Prevents users from guessing signed URLs or modifying their transformations. | Fully restricts access to assets and transformations. |
| **Enforcement Mechanism** |||
| [Signed URLs](#enforcement_mechanism_signed_delivery_urls)  | Authorizes access to `private` and `authenticated` assets, and dynamic transformations when using **strict transformations**. **Caution:** Doesn't prevent link sharing. Once a URL is exposed (e.g., on your website), anyone with it can access the asset.| Validating URL authenticity and restricting unauthorized access.  |

> **NOTES**:
>
> :title=Footnotes
> 1.   To prevent sensitive private and authenticated assets from appearing in search engine results, use the `explicit` endpoint's optional [header](image_upload_api_reference#explicit_optional_parameters) parameter to include `X-Robots-Tag: noindex` when uploading or configuring the asset's response headers.
> 2.  You'll also need to sign delivery URLs when using transformation add-ons that require it (e.g., [OCR Text Extraction](ocr_text_detection_and_extraction_addon) and [Cloudinary AI Content Analysis](cloudinary_ai_content_analysis_addon)), or when delivering assets using the [fetch](fetch_remote_images#fetch_and_deliver_remote_files) delivery type if **Fetched URL** is selected as a **Restricted media type** in your product environment settings.
> {/note}
> ### Prevent search engine indexing
> If you want to exclude assets from search engine results:

> * **On upload**: Pass response headers using the Upload API headers parameter, for example:
>     ```curl 
    curl -X POST "https://api.cloudinary.com/v1_1/<cloud_name>/image/upload" \
      -F "file=@/path/to/file.jpg" \
      -F "api_key=<api_key>" \
      -F "timestamp=$(date +%s)" \
      -F "signature=<signature>" \
      -F 'headers=X-Robots-Tag: noindex'
    ```
>     See [Upload API](image_upload_api_reference#upload) for more details.

> * **For existing assets**: Use the [explicit method](image_upload_api_reference#explicit) to (re)configure response headers without re-uploading (e.g., add `X‑Robots‑Tag: noindex`).

> * **Via upload presets**: You can add the same headers setting to an [upload preset](upload_presets) and apply it consistently wherever that preset is used (API, Upload Widget, or Console UI).
> {note}
> Excluding assets from indexing doesn't change access permissions. To fully restrict who can view assets, combine this with the access‑control options on this page (e.g., private delivery types, authenticated delivery types, and signed URLs).
> {/note}
> ### Control who can access assets and when
> These options prevent access to existing files and control who can view them:
> {table:class=set-first-col} **Feature** | **What it Does** | **Best Used For** |
> |-------------|------------------|--------------------|
> | **Access Control** |||
> | [Access-controlled assets](#access_controlled_media_assets) | Requires a valid token or cookie to access. (Allows you to define time-limited public access.) | Gaining full control over who can access an asset and when. |
> | [Time-limited public access](#time_limited_public_access) | Allows public access during a specific time window for otherwise restricted assets. | Temporarily exposing an access-controlled asset before restricting it. |
> | [Token-based access](#token_based_access_premium_feature)Premium feature | Appends a signed token to URLs to restrict access by time, IP, or a specific URL pattern (using an Access Control List). | Delivering personalized or short-lived access (e.g., post-purchase, internal tools). |
> | [Cookie-based access](#cookie_based_access_premium_feature)Premium feature + CNAME | Similar to token-based access, but uses a signed browser cookie instead of a URL token. | Enforcing session-based access (e.g., logged-in web or mobile users). |
> | **Product Environment Rules** |||
> | [Product environment-level ACLs](#product_environment_access_control_list_premium_feature)Premium feature | Globally allows or blocks access based on IP, country, Referer, User-Agent, and more. | Blocking unwanted traffic (e.g., bots, regions, or referrers) at the environment level. |
> 
> {reading}
> For guidance on where Cloudinary access controls fit within a broader traffic management strategy, and when to use upstream CDN or WAF controls instead, see [Bot traffic, usage spikes, and access control best practices](ts_bot_traffic_usage_spikes_access_control_best_practices).
> {/reading}
> ## Strict transformations
> Cloudinary transformation URLs are dynamic by default. If a transformed version doesn't exist, it's generated on the fly. While flexible, this can lead to unintended usage (e.g., transformation spamming or bypassing overlays). 
> To prevent this, you can enable **Strict Transformations** from the [Security](https://console.cloudinary.com/app/settings/security) page in your Cloudinary Console. Once enabled, transformations can only be created through:

> * [Eager transformations](eager_and_incoming_transformations#eager_transformations)

> * [Signed delivery URL](#enforcement_mechanism_signed_delivery_urls)

> * [Allowed name transformations](#allow_transformation)

> * [Allowed referral domains](#allowed_strict_referral_domains)
> {tip}
> For a broader look at protecting against bot-driven abuse, see [Bot traffic, usage spikes, and access control best practices](ts_bot_traffic_usage_spikes_access_control_best_practices).
> {/tip}
> 
> ### Define allowed transformations
> Once you've enabled strict transformations for your product environment, you can allow specific transformations either using the Admin API or the Console UI.
> This section covers the following:

> * [Using the Admin API](#using_the_admin_api)

> * [Using the Console UI](#using_the_console_ui)

> * [Unique transformations](#unique_transformations)

> * [Using automatic format in strict transformations](#using_automatic_format_in_strict_transformations)

> * [Using Client Hints with Strict Transformations](#using_client_hints_with_strict_transformations)
> #### Using the Admin API
> Using the [update transformation](admin_api#update_transformation) method of the Admin API, set `allowed_for_strict` to `true` for the transformation you want to allow.
> For example, allow a transformation by name (here, `on_sale_overlay` is a [named transformation](named_transformations)):
> ```multi

|ruby
result = Cloudinary::Api
.update_transformation('on_sale_overlay',
  allowed_for_strict: true)

|php_2
$result = $api
->updateTransformation("on_sale_overlay",
  ["allowed_for_strict" => 1]);

result = cloudinary.api\
.update_transformation("on_sale_overlay",
  allowed_for_strict = True)

|nodejs
cloudinary.v2.api
.update_transformation('on_sale_overlay',
  { allowed_for_strict: true })
.then(result=>console.log(result)); 

|java
result = api
.updateTransformation("on_sale_overlay",
  ObjectUtils.asMap("allowed_for_strict", true));

|csharp
var updateTransformParams = new UpdateTransformParams(){
  Transformation = "on_sale_overlay",
  Strict = true};
cloudinary.UpdateTransform(updateTransformParams);

|go
resp, err := cld.Admin.UpdateTransformation(ctx, admin.UpdateTransformationParams{
      Transformation:   "on_sale_overlay", 
      AllowedForStrict: api.Bool(true)})

|curl
curl \
  -d 'allowed_for_strict=true' \
  -X PUT \
  https://<API_KEY>:<API_SECRET>@api.cloudinary.com/v1_1/<CLOUD_NAME>/transformations/on_sale_overlay

|cli
cld admin update_transformation "on_sale_overlay" allowed_for_strict=true

```
> Or, allow a transformation by parameter listing:
> ```multi

|ruby
result = Cloudinary::Api
.update_transformation(
  { width: 150, height: 100, crop: "fill"},
  { allowed_for_strict: true })

|php_2
$result = $api
->updateTransformation(
  ["width" => 150, "height" => 100,"crop" => "fill"],
  ["allowed_for_strict" => 1]);

|python
result = cloudinary.api\
.update_transformation(
  dict(width = 150, height = 100, crop = "fill"), 
  allowed_for_strict = True)

|nodejs
cloudinary.v2.api
.update_transformation(
  { width: 150, height: 100, crop: 'fill' },
  { allowed_for_strict: true })
.then(result=>console.log(result)); 

|java
result = api
.updateTransformation(
  new Transformation().width(150).height(100).crop("fill").generate(), 
  ObjectUtils.asMap("allowed_for_strict", true));

|csharp
var updateTransformParams = new UpdateTransformParams(){
  Transformation = "w_150,h_100,c_fill",
  Strict = true};
cloudinary.UpdateTransform(updateTransformParams);

|go
resp, err := cld.Admin.UpdateTransformation(ctx, admin.UpdateTransformationParams{
      Transformation:   "w_150,h_100,c_fill", 
      AllowedForStrict: true})

|curl
curl \
  -d 'allowed_for_strict=true' \
  -X PUT \
  https://<API_KEY>:<API_SECRET>@api.cloudinary.com/v1_1/<CLOUD_NAME>/transformations/c_fill,h_100,w_150

|cli
cld admin update_transformation '{"width": 150, "height": 100, "crop": "fill"}' allowed_for_strict=true

```
> {note}
> If running the CLI command on Windows, you need to escape the double quotes within the curly braces using either `\` or `"`, for example, `\"text\"` or `""text""`.
> {/note}
> 
> #### Using the Console UI
> Open the **Manage Transformations** page in the **Image** product of the Console: **[Image > Manage Transformations](https://console.cloudinary.com/app/image/manage/)**. 
> You can find your transformations listed in both the [Named Transformations](https://console.cloudinary.com/app/image/manage/named) or [Log](https://console.cloudinary.com/app/image/manage/log) tabs. 
> For each relevant transformation, open the kebab menu and toggle the **Allowed for strict transformations** option.
> ![enable transformation](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/enable-strict-transformations-ui.png "thumb: c_scale,w_650/dpr_2, width: 650px")
> Once allowed for strict transformations, you can use the transformation on any asset to generate and deliver the transformed version of that asset on the fly. 
> For transformations that aren't allowed, if you try to apply the transformation to an asset you'll see a 404 error.
>  
> #### Unique transformations
> Some transformations deliver the same result, but aren't seen as identical by Cloudinary. Therefore, ensure you allow each variation of a transformation as required. Here are some examples where you may think the transformation is the same, but Cloudinary sees it as a different transformation:

> * If the order of the transformation parameters changes. For example, `w_300,c_scale` is considered a different transformation to `c_scale,w_300`, even though the delivered media looks the same.

> * If the file extension is different. For example, `.jpg` is considered a different transformation than `.jpeg`, or a JPEG file with no extension specified, even though a JPEG file is the delivered format in each case.

> * Whether or not the file format is specified as a transformation. For example, specifying `f_jpg` as a transformation parameter for a JPEG file is considered a different transformation than delivering the same transformation with a `.jpg` extension, even though a JPEG file is delivered in each case.
> {info}
> There are certain considerations to be aware of if enabling strict transformations after using your Cloudinary product environment for some time. For example, any derived asset generated while a particular transformation was still allowed, remains accessible until the cache is invalidated. You'll need to adapt certain transformation combinations to work with Strict Transformations enabled, so we recommend that you [contact us](https://support.cloudinary.com/hc/en-us/requests/new) to help identify any potential issues with the transition.
> {/info}
> #### Using automatic format in strict transformations
> You can specify [f_auto (automatic format)](transformation_reference#f_auto) in a transformation that you want to allow when you have strict transformations enabled because Cloudinary allows all format variations for the transformation in this case.
> You can't use `f_auto` directly within a [named transformation](named_transformations), so you need to combine `f_auto` and your named transformation, and then allow that transformation. 
> Here's one way to do that if you already have strict transformations enabled:
> 1. Create a new video transformation using the [Transformation Builder](https://console.cloudinary.com/app/image/transformation_builder) or an image transformation using [Studio](https://console.cloudinary.com/app/image/studio). Save the transformation as a named transformation/template. In this example, it has the name `scale356`. Notice that the builder signs the delivery URL if you've enabled strict transformations.
>       ![Studio with saved template](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/studio-named-tx.png "thumb: c_scale,w_600/dpr_2, width: 600px")
> 1. Use the [Cloudinary CLI](cloudinary_cli#url) to create a signed URL for that named transformation together with the `f_auto` parameter. You can use any asset; in this example we use `cld-sample-5.jpg`:
>       ```
      cld url -s cld-sample-5.jpg 't_scale356/f_auto'
      ```
> 1. The CLI returns a signed URL, but doesn't create the derived asset. To create the derived asset, causing the `t_scale356/f_auto` transformation to be visible in the Log, use a cURL command to request the derived asset, for example:
>       ```
      curl https://res.cloudinary.com/my_cloud/image/upload/s--gSk8FE-8--/t_scale356/f_auto/cld-sample-5.jpg --output cld-sample-5.jpg
      ``` 
> 1. You'll then see the transformation `t_scale356/f_auto` present in the [Log](https://console.cloudinary.com/app/image/manage/log), which you can [allow for strict transformations](#using_the_ui).
>       ![Allow f_auto with a named transformation](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/allow-fauto-strict-tx.png "thumb: c_scale,w_600/dpr_2, width: 600px")
> #### Using Client Hints with Strict Transformations
> When **Strict Transformations** are enabled, Cloudinary only allows transformations that are explicitly approved. This includes transformations that use automatic values such as `dpr_auto`, which are resolved at delivery time based on **Client Hints**.
> Because `dpr_auto` resolves to a concrete DPR value (for example, `dpr_1.0` or `dpr_2.0`), you must explicitly allow the resolved transformation strings or use signed delivery URLs.
> ##### Allow specific resolved transformation variants
> If you are using Client Hints with a known set of expected DPR values, pre-allow each resolved transformation.
> For example, if your baseline transformation is:
> ```
c_scale,t_sc_thumb,w_400/f_auto/q_auto
```
> You should allow the following resolved variants:
> ```
c_scale,dpr_1.0,t_sc_thumb,w_400/f_auto/q_auto
c_scale,dpr_2.0,t_sc_thumb,w_400/f_auto/q_auto
```
> Then mark each transformation as **Allowed for strict transformations** [using the Console UI](#using_the_console_ui) or the [Admin API](#using_the_admin_api).
> Alternatively, instead of enumerating all possible resolved variants, you can generate delivery URLs using [signed URLs](#enforcement_mechanism_signed_delivery_urls). Signed URLs allow dynamic transformations while Strict Transformations are enabled, without requiring each transformation to be pre-approved.
> This approach is recommended when:

> * You expect many possible DPR or size variations

> * Transformations are generated dynamically per request
> {tip}
> Signed URLs are often the simplest way to combine Strict Transformations with responsive images and Client Hints.
> {/tip}
> ### Allowed strict referral domains
> You can use the **Allowed strict referral domains** setting to set the referrer domains that you want to permit to generate unsigned transformations on the fly, even when you've got strict transformations enabled. Specify the domain(s) in the [Security](https://console.cloudinary.com/app/settings/security) page of the Cloudinary Console Settings. Separate multiple domain entries with a newline. 
> {note}
> Keep in mind that this option only determines which referral domains can _generate_ new unsigned transformations.  It doesn't restrict other domains from accessing a transformation URL once the transformed asset is generated (via a signed request or by an allowed domain).
> {/note}
> ## Restrictive delivery types
> Delivery types control access behavior and must be set at upload using the type parameter. These delivery types do not work in isolation. They require [signed URLs](#enforcement_mechanism_signed_delivery_urls) and may integrate with asset-level [access control](#access_controlled_media_assets).
> ### Private media assets
> When you upload images and videos as [private](upload_parameters#private_assets), the original file is protected and only accessible via a [signed URL](#enforcement_mechanism_signed_delivery_urls) However, by default, any derived (transformed) versions of the asset are publicly accessible. 
> For example, the following delivery URL for the `sample` image returns an error:
> `https://res.cloudinary.com/demo/image/private/sample.jpg`
> But a derived version is accessible, such as the `sample` private image with a width of 300 pixels:
> `https://res.cloudinary.com/demo/image/private/w_300/sample.jpg`
> To deliver a derived version of a private asset, set the `type` parameter in the delivery URL to `private` (instead of the default `upload`).
> To prevent public access even to derived assets, you can enable [Strict Transformations](#strict_transformations) which blocks transformation URLs unless they are explicitly allowlisted or signed.
> #### Providing time-limited access to private media assets
> You can make a private asset temporarily accessible (for example, to enable a customer to access a stock photo on your site after purchasing it) by generating a time-limited and signed URL with the `private_download_url` method of the Cloudinary API. 
> The generated URL delivers the image via a secure authenticated API request to Cloudinary each time. The image isn't cached on the CDN. 
> {info}
> You shouldn't embed these time-limited URLs on your website as they cost twice the bandwidth when delivering them.
> {/info}
> **Required parameters**:

> * `public_id` - The identifier of the uploaded asset.

> * `format` - The asset file format. 
> **Optional parameters**:

> * `resource_type` - The resource type (`image`, `video` or `raw`) of the file to deliver. Default: `image`. 

> * `type` - The delivery type of the file to deliver. Possible values: `upload`, `private`, `authenticated`. Default: `private`.

> * `expires_at` - The date (UNIX time in seconds) for the URL expiration. Default: one hour from the time the URL is generated.

> * `attachment`- If `true`, the URL downloads the image as an attachment. Otherwise, the image is displayed. Default: `false`.

> * `transformation` - A transformation to apply to the delivered image or video (not currently supported by the SDKs).
> #### Example 1: Image with default expiry time 
> Generate a link to the `my_picID` image in JPEG format, which is valid for one hour:
> ```multi
|ruby
Cloudinary::Utils.private_download_url('my_picID', 'jpg')

|php_2
(new Cloudinary\Api\Admin\AdminApi())->privateDownloadUrl('my_picID', 'jpg');

|python
cloudinary.utils.private_download_url("my_picID", "jpg")

|nodejs
cloudinary.v2.utils.private_download_url('my_picID', 'jpg');

|java
cloudinary.utils().privateDownload("my_picID", "jpg", 
    ObjectUtils.emptyMap());

|csharp
cloudinary.DownloadPrivate("my_picID", format: "jpg");

|go
resp, err := cld.Upload.PrivateDownloadURL(uploader.PrivateDownloadURLParams{
		PublicID: "my_picID",
		Format:   "jpg"})

|cli
cld utils private_download_url "my_picID" "jpg"
``` 
> This generates a link similar to the following:
> `https://api.cloudinary.com/v1_1/private-demo/image/download?timestamp=1753601889&public_id=my_picID&format=jpg&signature=d994c2b972c30d84d33fde684aa377fc17878be6&api_key=824698761754661`
> #### Example 2: Video with extended expiry time
> Generate a link to the `my_video` video in MP4 format, which is valid for two hours:
> ```multi
|ruby
expires_time = Time.now.to_i + 7200  # 2 hours from now
Cloudinary::Utils.private_download_url('my_video', 'mp4', 
  expires_at: expires_time,
  resource_type: "video")

|php_2
$expires_time = time() + 7200;  // 2 hours from now
(new Cloudinary\Api\Admin\AdminApi())->privateDownloadUrl('my_video', 'mp4', [
  "expires_at" => $expires_time,
  "resource_type" => "video"
]);

|python
import time
expires_time = int(time.time()) + 7200  # 2 hours from now
cloudinary.utils.private_download_url("my_video", "mp4",
  expires_at=expires_time,
  resource_type="video")

|nodejs
const expires_time = Math.floor(Date.now() / 1000) + 7200;  // 2 hours from now
cloudinary.v2.utils.private_download_url('my_video', 'mp4', {
  expires_at: expires_time,
  resource_type: "video"
});

|java
long expiresTime = System.currentTimeMillis() / 1000 + 7200;  // 2 hours from now
cloudinary.utils().privateDownload("my_video", "mp4", 
    ObjectUtils.asMap(
      "expires_at", expiresTime,
      "resource_type", "video"));

|csharp
var expiresTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 7200;  // 2 hours from now
cloudinary.DownloadPrivate("my_video", 
  format: "mp4",
  expiresAt: expiresTime,
  resourceType: ResourceType.Video);

|go
expiresTime := time.Now().Unix() + 7200  // 2 hours from now
resp, err := cld.Upload.PrivateDownloadURL(uploader.PrivateDownloadURLParams{
		PublicID: "my_video",
		Format:   "mp4",
		ResourceType: "video",
		ExpiresAt: &expiresTime})

|cli
expires_time=$(date -v +2H +%s)  # 2 hours from now (macOS)
# or for Linux: expires_time=$(date -d '+2 hours' +%s)
cld utils private_download_url "my_video" "mp4" expires_at=$expires_time resource_type="video"
``` 
> This generates a link similar to the following:
> `https://api.cloudinary.com/v1_1/private-demo/video/download?timestamp=1753443184&public_id=my_video&format=mp4&expires_at=1753450172&signature=013b1ff88c8b7e81ca9eb93ee5b31cce86ca709d&api_key=824698761754661`
> ### Authenticated media assets
> When you upload images and videos as [authenticated](upload_parameters#authenticated_assets), the original file and any derived (transformed) versions are protected and only accessible via a [signed URL](#enforcement_mechanism_signed_delivery_urls). 
> For example, the following delivery URL for the `auth_sample` image returns an error:
> `https://res.cloudinary.com/demo/image/authenticated/auth_sample.jpg`
> Derived versions also return an error, such as the `auth_sample` image with a width of 300 pixels:
> `https://res.cloudinary.com/demo/image/authenticated/_w_300/auth_sample.jpg`
> To access the asset, you'll need to use the exact signature in the delivery URL: 
> ![Authenticated image](https://res.cloudinary.com/demo/image/authenticated/s--HILJKWs4--/v1753862893/auth_sample.jpg "with_image:false, with_url:true, with_code:false")
> You can’t create on-the-fly transformations for authenticated assets. Instead, you can only access derived assets that were generated ahead of time during upload or update using eager transformations. Each of these includes its own signature, which you can retrieve from the upload or update response.
> {note}
> The `authenticated` delivery type is an element of the URL. Therefore, an asset uploaded with this type is always an authenticated asset. Changing the type also requires changing the delivery URL, which can be done using the `to_type` parameter of the [Rename](image_upload_api_reference#rename_method) method.
> {/note}
> 
> ## Enforcement mechanism: Signed delivery URLs
> A signed Cloudinary delivery URL is a delivery URL that has its signature validated before making the asset available for view (see the article about [on-the-fly image transformations secured with signed urls](/blog/on_the_fly_image_manipulations_secured_with_signed_urls) for more details). Signed delivery URLs allow you to:

> * Deliver specific derived assets when [strict transformations](#strict_transformations) are enabled

> * Allow access to [private and authenticated](#restrictive_delivery_types) assets
> {note}
> You'll also need to sign delivery URLs when using transformation add-ons that require it (e.g., [OCR Text Extraction](ocr_text_detection_and_extraction_addon) and [Cloudinary AI Content Analysis](cloudinary_ai_content_analysis_addon)), or when delivering assets using the [fetch](fetch_remote_images#fetch_and_deliver_remote_files) delivery type if **Fetched URL** is selected as a **Restricted media type** in your product environment settings.
> {/note}
> A signed delivery URL contains a signature component of the format `/s--SIGNATURE--/`. The signature is automatically generated by Cloudinary's backend SDKs by adding the `sign_url` boolean parameter to the [helper method](image_transformations#embedding_images_in_web_pages) and setting it to true (you can manually generate a signature by taking the first eight characters of a base64 encoding of an SHA digest of a 'public_id/transformation' string concatenated with your API secret. See [Generating delivery URL signatures](delivery_url_signatures) for more information).
> {note}
> By default, Cloudinary supports both SHA-1 and SHA-256 digests for validation, and you can use either. The SDK methods use the SHA-1 algorithm by default, but you can use the SHA-256 algorithm instead by setting the `signature_algorithm` SDK [configuration parameter](cloudinary_sdks#configuration_parameters) to `sha256`. If you want to limit your account to allow only the SHA-256 digest for all your validations, [submit a request](https://support.cloudinary.com/hc/en-us/requests/new).
> {/note}
> For example, to create an image tag for the authenticated image `secret_couple` resized to a height and width of 300 pixels using the fill resize mode, while signing the transformation URL using the first eight characters of an SHA signature:
> ![signed image tag](https://res.cloudinary.com/demo/image/authenticated/s--77PWXXNB--/c_fill,h_300,w_300/v1515589189/secret_couple.jpg "with_url: false, with_image: false, frameworks: ruby,python,nodejs,java,csharp,php_2")
> ```ruby
cl_image_tag("secret_couple.jpg", height: 300, width: 300, crop: "fill", sign_url: true, type: "authenticated")
```
> And the same for a video tag, using the same transformations:
> ![signed video tag](https://res.cloudinary.com/demo/video/authenticated/s--77PWXXNB--/c_fill,h_300,w_300/dog.mp4 "with_url: false, with_image: false, frameworks: ruby,python,nodejs,java,csharp,php_2")
> ```ruby
cl_video_tag("dog", height: 300, width: 300, crop: "fill", sign_url: true, type: "authenticated")
```
> Once a derived image or video has been generated (whether dynamically on first access or [eagerly](eager_and_incoming_transformations#eager_transformations) during upload), the signature checking is skipped and the signature itself can be omitted (except for [Authenticated assets](#authenticated_media_assets), which always require either a signature or another access token).
> {warning}
> The signature covers both the public ID and any transformations in the URL. If you add or change a transformation in a signed URL, for example by adding the [no_stream flag](transformation_reference#fl_no_stream) (`fl_no_stream`) to an authenticated video, the signature no longer matches and Cloudinary returns a **401** error with `Unauthenticated access` in the `x-cld-error` response header. If [strict transformations](#strict_transformations) are enabled, an image URL with a mismatched signature returns a **401** error with `Transformation <transformation> is not allowed` in the `x-cld-error` header. Always generate the signature from the final delivery URL.
> {/warning}
> **See also:** [Delivery URL signatures](delivery_url_signatures).
> ## Access-controlled media assets
> **Access control** allows you to restrict the validity of your asset delivery URLs, allowing access only via:

> * [Public access during a defined time window](#time_limited_public_access) (optional)

> * [Token-based access](#token_based_access_premium_feature) to restrict access by time, IP address, or delivery path (ACL) *(premium feature)*  

> * [Signed cookie access](#cookie_based_access_premium_feature) based on session or path pattern *(premium feature + CNAME)*
> {info}
> Access control is available to all Cloudinary accounts. However, token-based and cookie-based access require the [Advanced plan](https://cloudinary.com/pricing) or higher. If your account doesn't support tokens, setting `access_type: token` will make the asset completely inaccessible, unless you define a public access time window.
> {/info}
> **To activate access control:** 

> * At a minimum, you must include `{ "access_type": "token" }` in the `access_control` array at [upload](image_upload_api_reference#upload) or [update](admin_api#update_details_of_an_existing_resource). This disables public access unless overridden by a matching token, signed cookie, or public time window rule.**Example:** Restrict the `sample.jpg` image to require secure delivery (via token, cookie, or public time window).
>     ```multi
    |ruby  
    result = Cloudinary::Uploader.upload("sample.jpg",
      access_control: [       
        { access_type: "token" }
      ])
    
    |php_2
    $result = $cloudinary->uploadApi()->upload("sample.jpg", [ 
        "access_control" => [
          ["access_type" => "token"]
        ]]);
      
    |php
    $result = \Cloudinary\Uploader::upload("sample.jpg", [ 
        "access_control" => [
          ["access_type" => "token"]
        ]]);
      
    |python
    result = cloudinary.uploader\.upload("sample.jpg", 
      access_control = [
        {"access_type": "token"}
      ])

    |nodejs
    cloudinary.v2.uploader
    .upload("sample.jpg", 
      { access_control: [
        { access_type: "token" }
      ]}) 
      .then(result=>console.log(result));
      
    |cli
    cld uploader upload "sample.jpg" access_control='[{"access_type": "token"}]'
    ```
>     {note}
> If running the CLI command on Windows, you need to escape the double quotes within the curly braces using either `\` or `"`, for example, `\"text\"` or `""text""`.
> {/note}
> {tip:title=Tips}

> * You can use access control in combination with delivery type restrictions.

> * You can also view or modify [access control settings in the Media Library](media_library_for_developers#access_control).
> {/tip}
> ### Time-limited public access
> You can include an `anonymous` access rule to make the asset temporarily available to the public during a defined time window, no token or authentication needed. Outside this window, access is blocked unless a valid token or cookie is provided.
> Use cases include:

> * Post-purchase downloads

> * Temporary event or campaign materials

> * Embargoed content
> **To set time-limited public access:**

> * Combine both `token` and `anonymous` rules in the `access_control` array. The asset will be delivered if either condition is valid.**Example:** Asset requires secure access, except between Dec 15, 2022 and Jan 20, 2024, when it's publicly accessible.
>     ```multi
    |ruby  
    result = Cloudinary::Uploader.upload("sample.jpg",
      access_control: [       
        { access_type: "token" },
        { access_type: "anonymous", start: "2022-12-15T12:00Z", end: "2023-01-20T12:00Z" }])
    
    |php_2
    $result = $cloudinary->uploadApi()->upload("sample.jpg", [ 
        "access_control" => [
          ["access_type" => "token"],
          ["access_type" => "anonymous", "start" => "2022-12-15T12:00Z", "end" => "2023-01-20T12:00Z" ]]]);
      
    |php
    $result = \Cloudinary\Uploader::upload("sample.jpg", [ 
        "access_control" => [
          ["access_type" => "token"],
          ["access_type" => "anonymous", "start" => "2022-12-15T12:00Z", "end" => "2023-01-20T12:00Z" ]]]);
      
    |python
    result = cloudinary.uploader\.upload("sample.jpg", 
      access_control = [
        {"access_type": "token"},
        {"access_type": "anonymous", "start": "2022-12-15T12:00Z", "end": "2023-01-20T12:00Z" }])

    |nodejs
    cloudinary.v2.uploader
    .upload("sample.jpg", 
      { access_control: [
        { access_type: "token" }, 
        { access_type: "anonymous", start: "2022-12-15T12:00Z", end: "2023-01-20T12:00Z" } ]}) 
      .then(result=>console.log(result));
      
    |cli
    cld uploader upload "sample.jpg" access_control='[{"access_type": "token"}, {"access_type": "anonymous", "start": "2022-12-15T12:00Z", "end": "2023-01-20T12:00Z" }]'
    ```
> ### Token-based and cookie-based access (premium features)
> Token-based and cookie-based access let you flexibly and securely restrict the delivery of media assets using signed tokens. Both methods use the same token format and enforce the same access rules. Token-based access adds the token as a query string to the URL, while cookie-based access stores the token in a browser cookie.
> These features are available on the [Advanced plan](https://cloudinary.com/pricing) or higher, and Cloudinary enables them per product environment. [Contact support](https://support.cloudinary.com/hc/en-us/requests/new) with your cloud name and ask them to:

> * Enable token-based access, and cookie-based access if you need it.

> * Send you the encryption key provisioned for that environment.
> Cloudinary configures token validation at the CDN level as part of the setup. The encryption key is a hexadecimal string and isn't your API secret. You need it to generate tokens and cookies, so keep it server-side and treat it as a secret.
> {info}
> Cookie-based access additionally requires using a [Custom delivery hostname (CNAME)](advanced_url_delivery_options#private_cdns_and_custom_delivery_hostnames_cnames) at additional cost. Because those delivery URLs don't include your cloud name, cookie `acl` patterns don't include it either: see [Matching the acl pattern to your delivery URL](#matching_the_acl_pattern_to_your_delivery_url).
> {/info}
> The following access rules apply to both token-based and cookie-based access:

> * A specific time frame or expiration date

> * A specific IP address

> * A specific URL pattern (using an Access Control List)
> To access an asset with `"access_type": "token"` (outside of any [public access window](#time_limited_public_access), if defined) the request must include a valid signed token.
> {info}
> Before you start, make sure that you have set up your framework to access Cloudinary's APIs. For details, see the relevant [SDK framework documentation](cloudinary_sdks).
> {/info}
> #### Generating access tokens (for token-based and cookie-based access)
> For token-based (URL) access, pass the token parameters to a Cloudinary SDK method (e.g., `cl_image_tag` in Rails) using `auth_token` and `sign_url: true`, and the SDK generates and signs the token internally.
> For cookie-based access, use the `generate_auth_token` method to generate the token string, which you then set as a browser cookie.
> **Shared token parameters**
> Both token-based (URL) and cookie-based access use the following parameters to define when and how to grant access:

> * `key` – (Required) - the token must be signed with the encryption key received from Cloudinary.

> * `acl` – (Optional) – an Access Control List that limits the token to the delivery URLs whose path matches a pattern you specify, for example `/my_cloud/image/authenticated/*`. One token with an `acl` pattern can cover many URLs, while a token without it covers only the single URL you generate it for. The pattern you need depends on how you deliver your assets, and it can also pin down the transformation that's applied. For the details, see [Matching the acl pattern to your delivery URL](#matching_the_acl_pattern_to_your_delivery_url).

> * `ip` – (Optional) - only this IP address can access the resource.

> * `start_time` - (Optional) - timestamp of the UNIX time when the URL becomes valid. Default value: the current time.

> * `duration` – (Optional)1 – the duration that the URL is valid in seconds (counted from *start\_time*).

> * `expiration` - (Optional)1 - timestamp in UNIX time when the URL expires.
> 1 Either `duration` or `expiration` must be provided (if both are provided, `duration` is ignored).
> Each optional parameter applies only to the token you pass it to, and only if you pass it: leaving `ip` out, for example, places no IP restriction on the token. So pass a parameter every time you generate a token that needs the restriction it enforces. `start_time` is always optional: if you pass `duration` instead of `expiration`, the expiration is calculated from `start_time`, or from the current time if you don't pass one. The token's `start_time` and `expiration` are separate from any [public access window](#time_limited_public_access) set on the asset itself: where both are defined, the asset is delivered if either condition is valid.
> #### Matching the acl pattern to your delivery URL
> Use `acl` when one token needs to cover more than one delivery URL. The pattern you give it defines the URL paths that the token is valid for, so a single token can cover, for example, every authenticated image in your product environment, or every URL that applies a particular transformation. Without `acl`, the token is signed against the full path of the single URL you generate it for, including the cloud name on the shared CDN. Such a token grants access to that exact path only, so adding or changing a transformation invalidates it.
> **Where the pattern is matched**
> The pattern is matched at the CDN against the **path** of the delivery URL, meaning everything after the delivery hostname. That path depends on where you deliver from, so a pattern written for one delivery hostname doesn't work on the other:
> | Delivering from | Example URL path | Pattern for all authenticated images |
> |---|---|---|
> | Shared CDN: `https://res.cloudinary.com/<cloud_name>/...` | `/my_cloud/image/authenticated/sample.jpg` | `/my_cloud/image/authenticated/*` |
> | [Private CDN or custom delivery hostname (CNAME)](advanced_url_delivery_options#private_cdns_and_custom_delivery_hostnames_cnames): `https://<your_hostname>/...` | `/image/authenticated/sample.jpg` | `/image/authenticated/*` |
> A pattern that begins with `*` matches any prefix, which is why patterns such as `*/image/authenticated/*` work on the shared CDN: the leading `*` covers the cloud name.
> {notes}

> * On the shared CDN, a pattern that starts at the asset type, such as `/image/authenticated/*`, doesn't match the path, because the path starts with your cloud name. Start the pattern with your cloud name or with `*`.

> * Cookie-based access requires a [custom delivery hostname (CNAME)](advanced_url_delivery_options#private_cdns_and_custom_delivery_hostnames_cnames), and those URL paths don't include your cloud name. So for cookie-based access, start the pattern at the asset type, and don't include the cloud name. Include the cloud name only for token-based access delivered from `res.cloudinary.com`.

**What else the pattern can include**

* The pattern can include any of Cloudinary's transformations to also apply to the delivered assets, and restrict access to a specific path, or even to a specific asset. Note that if you add an overlay (e.g., for a watermark), you should also include the *fl\_layer\_apply* flag to ensure the layer can't be modified.
* A pattern that includes a transformation only matches URLs with that exact transformation. If you add a parameter to the delivery URL, for example the [no_stream flag](transformation_reference#fl_no_stream) (`fl_no_stream`), the URL no longer matches the pattern and the token doesn't grant access. Include the complete transformation, including all its parameters, in the pattern, or generate the token from the final delivery URL.
* You can pass multiple path patterns (as an array, list, or varargs, depending on the SDK), and the SDK joins them with `!` before generating the token. The Go SDK only accepts `acl` as a single string, so to use multiple patterns in Go, join them yourself with `!` before assigning.

#### Generating a token without an SDK

The [SDKs](cloudinary_sdks) build and sign the token for you, and they're the recommended option. If you're working somewhere without a Cloudinary SDK, for example a shell script or a language Cloudinary doesn't provide an SDK for, build the token as follows.

A token is a `~`-delimited list of fields, ending with the HMAC of the fields before it:

```
__cld_token__=ip=<ip>~st=<st>~exp=<exp>~acl=<acl>~hmac=<hmac>
```

1. Assemble the fields in this exact order, including only the ones this token needs: `ip`, `st` (start time), `exp` (expiration), `acl`. The field order is part of the string that gets signed, so it can't vary. `st` and `exp` are UNIX timestamps in seconds.

    `exp` is always required, and so is either an `acl` field or the signed URL path described in step 4. `ip`, `st`, and `acl` are each optional in the sense that they add a restriction only when they're present: the CDN can enforce only what's in the token. So include `ip` when the token should work from one address only, `st` when the token shouldn't be valid straight away, and `acl` when the token needs to cover more than the one URL path you sign.
1. Percent-escape the `acl` value, escaping each of these characters, and write the hex digits in **lowercase** (`%2f`, not `%2F`): space `"` `#` `%` `&` `'` `/` `:` `;` `<` `=` `>` `?` `@` `[` `\` `]` `^` `` ` `` `{` `|` `}` `~`. Leave every other character unchanged, including `*` and `,`.
1. To use several patterns in one token, escape each pattern and join them with `!` inside a single `acl` field.
1. If you're not using `acl`, append `url=` to the string you sign, using the same escaping as in step 2. The `escaped_url_path` is the path of the one delivery URL this token is for, meaning everything after the delivery hostname, with no scheme or hostname, starting with `/`, and including any transformation and version segments, plus your cloud name if you deliver from the shared CDN. For example, `/my_cloud/image/authenticated/v1717000000/sample.jpg` escapes to `%2fmy_cloud%2fimage%2fauthenticated%2fv1717000000%2fsample.jpg`. This field is signed but isn't included in the token itself, because the CDN takes the path from the request.
1. Join the fields with `~` and compute an HMAC-SHA256 over that string. The encryption key is hexadecimal, so decode it to bytes before using it as the HMAC key. Format the digest as lowercase hex.
1. Append the digest as a final `hmac` field. For token-based access, add the whole `__cld_token__=...` string to the delivery URL as its query string. For cookie-based access, the string is already in the `name=value` form the `__cld_token__` cookie needs: see [Cookie placement](#cookie_placement).

This script generates a token for all authenticated images in the `my_cloud` product environment on the shared CDN:

```bash
KEY="00112233ff99"                      # the encryption key you received from Cloudinary
ACL="/my_cloud/image/authenticated/*"   # on res.cloudinary.com, start with /<cloud_name>
EXP=$(( $(date +%s) + 300 ))            # expires in 5 minutes

escape() {
  local s=$1 out= i c
  for (( i=0; i<${#s}; i++ )); do
    c=${s:$i:1}
    case $c in
      ' '|'"'|'#'|'%'|'&'|"'"|'/'|':'|';'|'<'|'='|'>'|'?'|'@'|'['|'\'|']'|'^'|'`'|'{'|'|'|'}'|'~')
        out+=$(printf '%%%02x' "'$c") ;;
      *) out+=$c ;;
    esac
  done
  printf '%s' "$out"
}

TO_SIGN="exp=$EXP~acl=$(escape "$ACL")"
HMAC=$(printf '%s' "$TO_SIGN" | openssl dgst -sha256 -mac HMAC -macopt "hexkey:$KEY" -r | cut -d' ' -f1)
echo "__cld_token__=$TO_SIGN~hmac=$HMAC"
```

Add the result to the delivery URL as its query string:

```
https://res.cloudinary.com/my_cloud/image/authenticated/sample.jpg?__cld_token__=exp=1717003600~acl=%2fmy_cloud%2fimage%2fauthenticated%2f*~hmac=85e38bc4db12cb83d6adf190d0c98dadc81ee36e5b47bf66d06e5463b47bd762
```

**Test vectors**

Use these to check your implementation. They all use the encryption key `00112233ff99`:

* `exp=1717003600`, `acl=/my_cloud/image/authenticated/*`

  ```
  __cld_token__=exp=1717003600~acl=%2fmy_cloud%2fimage%2fauthenticated%2f*~hmac=85e38bc4db12cb83d6adf190d0c98dadc81ee36e5b47bf66d06e5463b47bd762
  ```

* `ip=203.0.113.7`, `st=1717000000`, `exp=1717003600`, `acl=/my_cloud/image/authenticated/*`

  ```
  __cld_token__=ip=203.0.113.7~st=1717000000~exp=1717003600~acl=%2fmy_cloud%2fimage%2fauthenticated%2f*~hmac=f6186f3e80d97b493d38b6f82cb11338df9caa57831498cf6746106194ad85a9
  ```

* `exp=1717003600`, `acl=/my_cloud/image/authenticated/*` and `/my_cloud/video/authenticated/*`

  ```
  __cld_token__=exp=1717003600~acl=%2fmy_cloud%2fimage%2fauthenticated%2f*!%2fmy_cloud%2fvideo%2fauthenticated%2f*~hmac=3b91fb84e9f73e0f421b4723bd15b97b411b6d59e39fb2fcac1c1ac2a1ecba18
  ```

* `exp=1717003600` with no `acl`, for the URL path `/my_cloud/image/authenticated/v1717000000/sample.jpg`. The string you sign is:

  ```
  exp=1717003600~url=%2fmy_cloud%2fimage%2fauthenticated%2fv1717000000%2fsample.jpg
  ```

  and the resulting token leaves the `url` field out:

  ```
  __cld_token__=exp=1717003600~hmac=651c19e89a26ce58e47b5038db93b4d0861cd6f048c47cf1b3d8075b22da67a5
  ```

#### Troubleshooting a rejected token

Cloudinary denies a rejected token with a 401, and the response carries an `X-Cld-Error` header: see [How to diagnose 40x and X-Cld-Error codes](ts_what_are_the_common_error_codes_returned_in_the_x_cld_error_header_when_delivering_assets#401_unauthorized). The header doesn't distinguish a bad `acl` from a wrong key, an expired token, or a product environment that isn't configured for token access, so check these causes in order:

1. **The `acl` pattern starts in the wrong place.** On `res.cloudinary.com` the pattern needs your cloud name or a leading `*`. On a private CDN or custom delivery hostname, including the one that cookie-based access requires, the path has no cloud name, so the pattern must not include it either. See [Matching the acl pattern to your delivery URL](#matching_the_acl_pattern_to_your_delivery_url).
1. **The `exp` time has passed.** It's in seconds, so a value in milliseconds reads as a date in 1970 and the token is already expired.
1. **The `st` time hasn't arrived yet.** A token isn't valid before its start time, so an `st` in the future is rejected until then. Leave `st` out unless you need the token to become valid later.
1. **The clock on the server that signs the token doesn't match the CDN's clock.** The CDN compares `st` and `exp` against its own clock, so if your server's clock is slightly ahead or behind, the CDN can treat a token as not yet valid or as already expired. This usually shows up as tokens that fail only some of the time, near the start or the end of the period they're valid for. Keep your server's clock synchronized, and leave a margin of a few seconds instead of setting `st` and `exp` to exactly the times you need.
1. **The `ip` doesn't match.** The address has to be the one the request reaches Cloudinary from, which isn't the client's address if a proxy sits in front of it.
1. **The token is signed with the wrong secret.** Tokens use the encryption key Cloudinary provisioned for the product environment, not your API secret.
1. **The delivered URL no longer matches a token generated without `acl`.** Such a token covers one exact path, so any change to the transformation or version invalidates it.

If you [generate tokens without an SDK](#generating_a_token_without_an_sdk), also check that you decoded the hex key to bytes before signing, escaped only the listed characters using lowercase hex digits, kept the fields in the order `ip`, `st`, `exp`, `acl`, and signed exactly the string you send.

<a class="anchor" name="setting_up_token_based_authentication"></a><a class="anchor" name="token_based_authentication_premium_feature"></a><a class="anchor" name="delivering_token_based_authenticated_media_assets"></a><a class="anchor" name="token_based_access_premium_feature">

#### Delivering token-based media assets

When using Cloudinary SDKs to create delivery URLs (e.g., `cl_image_tag` or `cl_video_tag` in Rails), you can include a token in the URL by adding:

* `sign_url: true`
* `auth_token: { ... }` with the [shared token parameters](#generating_access_tokens_for_token_based_and_cookie_based_access)

**Example 1**: To create a token-based access URL that restricts access to the `sample` authenticated image for 5 minutes and is signed with 'MyKey': 

```multi
|ruby
cl_image_tag("sample.jpg", 
  sign_url: true, 
  auth_token: { key: "MyKey", duration: 300 }, 
  type: "authenticated")
  
|php_2
ImageTag::fromParams("sample.jpg",  
  ["auth_token" => [
  	"key" => "MyKey", "duration" => 300], 
  "type" => "authenticated",  
  "sign_url" => true]); 

|python
cloudinary.CloudinaryImage("sample.jpg").image(
  type = "authenticated", 
  auth_token = dict(key = "MyKey", duration = 300),
  sign_url = True)

|nodejs
cloudinary.image("sample.jpg", { 
  type: "authenticated",  
  auth_token: {key: "MyKey", duration: 300 },
  sign_url: true })

|java
cloudinary.url().transformation(new Transformation())
  .type("authenticated")
  .authToken(new AuthToken("MyKey").duration(300))
  .signed(true)
  .imageTag("sample.jpg");

|csharp
AuthToken t = new AuthToken("MyKey").Duration(300);
string url = cloudinary.Api.UrlImgUp
  .Signed(true)
  .AuthToken(t)
  .Type("authenticated")
  .BuildUrl("sample.jpg");

|go
img, err := cld.Image("sample.jpg")
img.DeliveryType = api.Authenticated
img.Config.URL.SignURL = true
img.AuthToken.Config = &config.AuthToken{
  Key: "MyKey",
  Duration: 300}
```
<br/>

**Example 2**: To create a token-based access URL that restricts access to the `dog` authenticated video until 1/1/2018 (1514764800 in UNIX time) and is signed with 'MyKey': 

```multi

|ruby
cl_video_tag("dog.mp4", 
  sign_url: true, 
  auth_token: { key: "MyKey", expiration: 1514764800 }, 
  type: "authenticated")
  
|php_2
VideoTag::fromParams("dog.mp4",  ["auth_token" => ["key" => "MyKey", "expiration" => 1514764800], 
  "type" => "authenticated", "sign_url" => true]); 

|python
cloudinary.CloudinaryVideo("dog.mp4").video(
  type = "authenticated", 
  auth_token = dict(key = "MyKey", expiration = 1514764800), 
  sign_url = True)

|nodejs
cloudinary.video("dog.mp4", { 
  type: "authenticated",  
  auth_token: {key: "MyKey", expiration: 1514764800 },
  sign_url: true })

|java
cloudinary.url().transformation(new Transformation())
  .type("authenticated")
  .authToken(new AuthToken("MyKey").expiration(1514764800))
  .signed(true))
  .videoTag("dog.mp4");

|csharp
AuthToken t = new AuthToken("MyKey").Expiration(1514764800);
string url = cloudinary.Api.Url
  .AuthToken(t)
  .Signed(true)
  .Type("authenticated")
  .BuildVideoTag("dog.mp4");

|go
dogVideo, err := cld.Video("dog.mp4")
dogVideo.DeliveryType = api.Authenticated
dogVideo.Config.URL.SignURL = true
dogVideo.Config.AuthToken = config.AuthToken{
	Key:        "MyKey",
	Expiration: 1514764800}
```

<a class="anchor" name="setting_up_cookie_based_authentication"></a><a class="anchor" name="cookie_based_access_premium_feature"></a>
<a class="anchor" name="cookie_based_authentication_premium_feature"></a><a class="anchor" name="creating_the_authentication_cookie"></a>
#### Creating the access cookie

To create a cookie based access token, you pass the [shared token parameters](#generating_access_tokens_for_token_based_and_cookie_based_access) to `generate_auth_token`, and set the resulting string as the value of a browser cookie named `__cld_token__`.

**Example 1**: To create a cookie that's valid for 5 minutes (300 seconds), allows access to all authenticated images, and is signed with `MY_KEY`: 

```multi

|ruby
tokenOptions  =  { 
  key: "MY_KEY", 
  duration: 300, 
  acl: "*/image/authenticated/*"
 }
 cookieToken = Cloudinary::Utils.generate_auth_token(tokenOptions)

|php_2
use Cloudinary\Asset\AuthToken;
$authToken = new AuthToken([
    'key' => 'MY_KEY',
    'duration' => 300,
    'acl' => '*/image/authenticated/*'
]);
$token = $authToken->generate();

|python
import cloudinary
import cloudinary.utils

options = {
    "duration": 300,
    "acl": "*/image/authenticated/*",
    "key":"MY_KEY"
}   

cookie_token = cloudinary.utils.generate_auth_token(**options)

|nodejs
const cloudinary = require('cloudinary').v2;

const cookie_token = cloudinary.utils.generate_auth_token({
    duration: 300,
    acl: '*/image/authenticated/*',
    key: 'MY_KEY' 
})
```
<br/>
**Example 2**: For allowing access only to authenticated videos that are delivered with a specific [named transformation](named_transformations) (t_authorized): 

```multi
|ruby
acl: "*/video/authenticated/t_authorized/*"

|php_2
'acl' => '*/video/authenticated/t_authorized/*'

|python
"acl": "*/video/authenticated/t_authorized/*"

|nodejs
acl: '*/video/authenticated/t_authorized/*'

|java
.acl("*/video/authenticated/t_authorized/*")

|csharp
.Acl("*/video/authenticated/t_authorized/*")

|go
Acl: "*/video/authenticated/t_authorized/*"
```
<br/>
The named transformation may add a watermark with the name of the employee, group or role who is accessing the image. In this case, a named transformation should be created via the Cloudinary Console for every user, group or role (respectively). 

**Example 3**: For allowing access only when the name of the user is applied as a text overlay on the image: 

```multi
|ruby
acl: "*/image/authenticated/l_text:Arial_45_bold:" + current_user + ",co_white/fl_no_overflow,fl_layer_apply/*"

|php_2
'acl' => '*/image/authenticated/l_text:Arial_45_bold:' . $currentUser . ',co_white/fl_no_overflow,fl_layer_apply/*'

|python
"acl": "*/image/authenticated/l_text:Arial_45_bold:" + current_user + ",co_white/fl_no_overflow,fl_layer_apply/*"

|nodejs
acl: `*/image/authenticated/l_text:Arial_45_bold:${currentUser},co_white/fl_no_overflow,fl_layer_apply/*`

|java
.acl("*/image/authenticated/l_text:Arial_45_bold:" + currentUser + ",co_white/fl_no_overflow,fl_layer_apply/*")

|csharp
.Acl("*/image/authenticated/l_text:Arial_45_bold:" + currentUser + ",co_white/fl_no_overflow,fl_layer_apply/*")

|go
Acl: "*/image/authenticated/l_text:Arial_45_bold:" + currentUser + ",co_white/fl_no_overflow,fl_layer_apply/*"
```
<br/>
So the following URL will be allowed (assuming the current user is John Smith): 

```
https://res.cloudinary.com/demo/image/authenticated/l_text:Arial_45_bold:John Smith,co_white/fl_no_overflow,fl_layer_apply/sample.jpg
```
<br/>
But if a different user's name is applied, it won't match the cookie contents and will be unauthorized. 

**Example 4**: For allowing access to authenticated images and videos, where their public IDs start with 'eyes_only', only when the red, bold, 50-pixel-sized text 'Confidential' is added as a text overlay:  

```multi
|ruby
acl: ["*/image/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*", "*/video/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*"]

|php_2
'acl' => ['*/image/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*', '*/video/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*']

|python
"acl": ["*/image/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*", "*/video/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*"]

|nodejs
acl: ['*/image/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*', '*/video/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*']

|java
.acl("*/image/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*", "*/video/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*")

|csharp
.Acl("*/image/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*", "*/video/authenticated/l_text:Arial_50_bold:Confidential,co_red/fl_layer_apply/eyes_only*")
```
<br/>

> **INFO**: Node.js, Ruby, PHP, Python, Java, and C# all accept `acl` as either a single string or multiple path patterns (as an array, list, or varargs, depending on the SDK). Internally, each SDK joins multiple patterns with `!` before generating the token. The Go SDK currently only accepts `acl` as a single string, so to restrict access to multiple path patterns in Go, join them yourself with `!` before assigning, for example `Acl: "pattern1!pattern2"`.
<br/>

#### Cookie placement

The cookie token returned from the `generate_auth_token` method is a string that includes the cookie name (_always_ `__cld_token__`) and its value, for example:

```
__cld_token__=ip=203.0.113.7~exp=1717003600~acl=%2fimage%2fauthenticated%2f*~hmac=b8f1333089c649cb2ae33218cee75ae3fa51c2d4dad05726d6b8f90fac0f6689
```

Set the cookie on your main domain, e.g., _app.customer.com_ or on the sub-domain that points to Cloudinary, e.g., _images.app.customer.com_.

## Searching for access-controlled assets

You can use the [Search API](search_method) to find assets with specific access control restrictions. Access control settings are stored as context values and can be queried using the [search expressions](search_expressions) syntax.

To search for assets with a specific access control configuration, search for the exact value stored in the `context.access_control` context field.

**Examples:**

Find assets that require token-based access:

```
context."access_control" = "[{\"access_type\":\"token\"}]"
```

Find assets with one of several known access control configurations:

```
context."access_control" = "[{\"access_type\":\"token\"}]" OR context."access_control" = "[{\"access_type\":\"anonymous\"}]"
```

> **TIP**: When searching for multiple access control configurations, combine exact-match expressions using the `OR` operator.

> **NOTE**: Search expressions match the stored `access_control` context value. If your assets use multiple access control rules or include time-based access windows, the exact stored value must be used in the search expression.

For more details on search expressions and available operators, see the [Search expressions](search_expressions) guide.

<a class="anchor" name="domain_blacklisting_or_whitelisting_premium_feature"></a><a class="anchor" name="allowlisting_or_blocklisting_access_to_your_product_environments_assets_premium_feature"></a>
## Product environment Access Control List (premium feature)

For each product environment, you can configure an Access Control List (ACL). This controls who can access your assets based on their country, IP address, the path segment of the request URL, content type, Referer and User-Agent.  

> **NOTE**: These options are available only for Cloudinary accounts on a paid plan. See the [Pricing](https://cloudinary.com/pricing) page for comparing tiers and list prices. For how credits, quotas, and usage apply to paid plans, see [Paid plans](billing_and_plans#paid_plans).

To configure the ACL, navigate to the [Delivery](https://console.cloudinary.com/app/settings/delivery) page of the Console Settings and set the **ACL Settings**.

![ACL settings](https://cloudinary-res.cloudinary.com/image/upload/bo_1px_solid_gray/f_auto/q_auto/docs/acl_settings.png "thumb:c_scale,w_300,dpr_2.0, width:300")

* Use `*` to match any number of characters at the start, middle or end of a string.
* Use `|` to separate optional matches (OR operand).
* Use `!` to specify exceptions (NOT operand).
* Use `NULL` for testing non-existent Referer and User-Agent strings.   

The conditions are tested in the following order:

1. If any of the ALLOW conditions match, the request is allowed and none of the DENY conditions are considered. 
1. If none of the ALLOW conditions match, the DENY conditions are considered.  
1. If any of the DENY conditions match, the request is denied, otherwise it’s allowed.

You can test out the logic by setting values in the **ACL Tester** tab.

Examples...

1. Allow VPN IP:
     * **Allow by IP**: 204.187.88.10|8.8.8.8 
     * **Deny by IP**: *

1. Allow VPN IP or valid Referers:
     * **Allow by IP**: 204.187.88.10|8.8.8.8
     * **Allow by Referer**: shoesByColin.com\*|\*colin.shoes\*
     * **Deny by IP**: *

1. Block hotlinking Referers:
     * **Deny by Referer**: shoesByMiki.com\*|NULL

1. Allow Referers:
     * **Allow by Referer**: shoesByColin.com\*|\*colin.shoes\* 
     * **Deny by Referer**: *

1. Block scripting User-Agents:
     * **Deny by User-Agent**: curl\*|wget\*|NULL

Learn more about the ACL settings...

* Protocols, such as `https://` and `http://`, are not considered in the Referer URL matches.
* Referer and User-Agent strings are URL-decoded before they are tested. If the match contains a space character, or other non-alphanumeric characters, it should be URL-encoded (e.g. `%20` for a space). 
* Double quotes are automatically stripped. For example, a request with `Referer: "example.com"` would be denied if **Deny by Referer** was set to `example.com|google.com`.  For this reason, to match `Referer: ""` you need to specify `NULL`.
* All comparisons are case-insensitive.
* IP address matches only support glob matches and do not support CIDR matches. Therefore, you can use IPv4 masks of /32, /24, /16, or /8  for example, `204.187.88.10`, `204.187.88.*`, `204.187.*`, or `204.*`  respectively. Multiple IP addresses should be separated by a pipe `|` symbol like this: `204.187.88.10|104.287.17.12`.
* For IPv6, masks of /128, /112, /96, /80, /64, /48, /32, /16 are supported. You must use the full octet expanded form, rather than IPv6 abbreviated syntax. 
* IP matches use the TCP Socket connecting IP address. Users of VPN or TOR or other proxy services are opaque to the match. Cellular users who are roaming will likely also report the home MNO (Mobile Network Operator) and not the current roaming country. This is by design with LTE and will likely persist even with 5G.
* Likewise, country code matches, which are based on IP Addresses are also subject to the above variances and are not 100% reliable.
* Country code matches use the two letter [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country code such as `US`, `CA` and `IL`. Three letter codes, such as `USA` won't match.
* The `!` operator can be used for nuanced nested matches. For example, to allow all countries except CA and then deny those in CA using a Safari browser, set **Allow by Country Code** to `!CA` and **Deny by User-Agent** to `Safari`.

> **TIP**: Regardless of these settings, access to assets is still subject to the access control features described on this page.
