> ## Documentation Index
> Fetch the complete documentation index at: https://docs-dev.auth0-mintlify.app/llms.txt
> Use this file to discover all available pages before exploring further.

# User Consent and Third-Party Applications

> Learn how Auth0 handles user consent when applications request access to APIs on the user's behalf.

export const AuthCodeGroup = ({children, dropdown}) => {
  const [processedChildren, setProcessedChildren] = useState(children);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      unsubscribe = window.autorun(() => {
        const processChildren = node => {
          if (typeof node === "string") {
            let processedNode = node;
            for (const [key, value] of window.rootStore.variableStore.values.entries()) {
              const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
              processedNode = processedNode.replaceAll(new RegExp(escapedKey, "g"), value);
            }
            return processedNode;
          } else if (Array.isArray(node)) {
            return node.map(processChildren);
          } else if (node && node.props && node.props.children) {
            return {
              ...node,
              props: {
                ...node.props,
                children: processChildren(node.props.children)
              }
            };
          }
          return node;
        };
        setProcessedChildren(processChildren(children));
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  return <CodeGroup dropdown={dropdown}>{processedChildren}</CodeGroup>;
};

export const AuthCodeBlock = ({filename, icon, language, highlight, children}) => {
  const [displayText, setDisplayText] = useState(children);
  const [copyText, setCopyText] = useState(children);
  const wrapperRef = React.useRef(null);
  useEffect(() => {
    let unsubscribe = null;
    function init() {
      if (!window.autorun || !window.rootStore) {
        return;
      }
      unsubscribe = window.autorun(() => {
        let processedChildrenForDisplay = children;
        let processedChildrenForCopy = children;
        for (const [key, value] of window.rootStore.variableStore.values.entries()) {
          const escapedKey = key.replaceAll(/[.*+?^${}()|[\]\\]/g, (String.raw)`\$&`);
          let displayValue = value;
          if (key === "{yourClientSecret}" && value !== "{yourClientSecret}") {
            displayValue = value.substring(0, 3) + "*****MASKED*****";
          }
          processedChildrenForDisplay = processedChildrenForDisplay.replaceAll(new RegExp(escapedKey, "g"), displayValue);
          processedChildrenForCopy = processedChildrenForCopy.replaceAll(new RegExp(escapedKey, "g"), value);
        }
        setDisplayText(processedChildrenForDisplay);
        setCopyText(processedChildrenForCopy);
      });
    }
    if (window.rootStore) {
      init();
    } else {
      window.addEventListener("adu:storeReady", init);
    }
    return () => {
      window.removeEventListener("adu:storeReady", init);
      unsubscribe?.();
    };
  }, [children]);
  useEffect(() => {
    if (!wrapperRef.current) return;
    const originalWriteText = navigator.clipboard.writeText.bind(navigator.clipboard);
    let isOverriding = false;
    const handleClick = e => {
      const button = e.target.closest('[data-testid="copy-code-button"]');
      if (!button || !wrapperRef.current.contains(button)) return;
      isOverriding = true;
      navigator.clipboard.writeText = text => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
          return originalWriteText(copyText);
        }
        return originalWriteText(text);
      };
      setTimeout(() => {
        if (isOverriding) {
          isOverriding = false;
          navigator.clipboard.writeText = originalWriteText;
        }
      }, 100);
    };
    const wrapper = wrapperRef.current;
    wrapper.addEventListener('click', handleClick, true);
    return () => {
      wrapper.removeEventListener('click', handleClick, true);
      if (navigator.clipboard.writeText !== originalWriteText) {
        navigator.clipboard.writeText = originalWriteText;
      }
    };
  }, [copyText]);
  return <div ref={wrapperRef}>
      <CodeBlock filename={filename} icon={icon} language={language} lines highlight={highlight}>
        {displayText}
      </CodeBlock>
    </div>;
};

OAuth allows applications to access APIs on the user's behalf. Before an application can act on a user's behalf, the user must explicitly approve the requested permissions. This approval step is called user consent.

For [third-party applications](/docs/get-started/applications/third-party-applications), user consent is always required. The user must approve every authorization request. For [first-party applications](/docs/get-started/applications/first-party-and-third-party-applications), consent can be skipped when configured, because you control the application and trust it to act appropriately.

## Consent dialog

When a third-party application redirects a user to the `/authorize` endpoint and requests access to an API, Auth0 displays a consent dialog listing the permissions the application is requesting.

The following authorization request displays a consent dialog asking the user to approve the `read:posts` and `write:posts` permissions for the API:

```http wrap lines theme={null}
GET /authorize?
  client_id=tpc_THIRD_PARTY_CLIENT_ID
  &redirect_uri=https://partner.example.com/callback
  &response_type=code
  &scope=read:posts write:posts
  &audience=https://social.example.com
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256
  &state=STATE_VALUE
```

<Frame>
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/docs-dev/docs/images/cdy7uua7fh8z/5Cz3aZKw8RRVlMkc5Zl6x7/62ac54cbc470286d5c2139d47c604ebc/2025-02-28_14-57-52.png" alt="Authorization - User consent and applications - consent-dialog" />
</Frame>

If the user approves, Auth0 creates a user grant representing the user's consent to this combination of application, API, and requested scopes. The application receives an authorization code as usual.

Once consent has been given, the user does not see the consent dialog during subsequent logins until consent is revoked explicitly.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Third-party applications with enhanced security controls do not support OIDC scopes (`openid`, `profile`, `email`) in this release. The consent dialog shows API scopes only. OIDC support for third-party applications is planned for a future release.
</Callout>

## Scope descriptions

By default, the consent page uses scope names to prompt for the user's consent. As shown below, define scopes using the `action:resource_name` format for clear display:

<Frame>
  <img src="https://mintlify.s3.us-west-1.amazonaws.com/docs-dev/docs/images/cdy7uua7fh8z/3Z4Ofbj5yF7eg5cLfcauh9/556bab9e627b0ff68b20664d149f1483/Blog_API_Permissions_-_English.png" alt="Authorization - User consent and applications - Consent scopes" />
</Frame>

The consent page groups scopes for the same API and displays all actions in a single line. For example, the configuration above results in **Posts: read and write your posts**.

To display the **Description** field instead of the scope name, set the tenant's `use_scope_descriptions_for_consent` flag to `true`:

<AuthCodeGroup>
  ```bash cURL wrap lines theme={null}
  curl --request PATCH \
    --url 'https://YOUR_DOMAIN/api/v2/tenants/settings' \
    --header 'Authorization: Bearer YOUR_MANAGEMENT_API_TOKEN' \
    --header 'Content-Type: application/json' \
    --data '{ "flags": { "use_scope_descriptions_for_consent": true } }'
  ```
</AuthCodeGroup>

This setting affects consent prompts for all APIs on the tenant.

## Handle rejected permissions

When a user declines consent, the behavior depends on the application's [redirection policy](/docs/get-started/applications/third-party-applications/security-controls#redirect-protection):

* **`open_redirect_protection`** (default for third-party apps): Auth0 displays an error page instead of redirecting. This prevents open redirect attacks.
* **`allow_always`**: Auth0 redirects to the `redirect_uri` with an `access_denied` error:

```http wrap lines theme={null}
HTTP/1.1 302 Found
Location: https://partner.example.com/callback?
    error=access_denied
    &state=STATE_VALUE
```

## Skip consent for first-party applications

First-party applications can skip the consent dialog when the API has the **Allow Skipping User Consent** option enabled.

To navigate to the **Allow Skipping User Consent** toggle, select **Applications > APIs > (select the API) > Settings > Access Settings**.

Third-party applications always require consent and cannot skip the consent dialog.

<Callout icon="file-lines" color="#0EA5E9" iconType="regular">
  Even when consent is skipped for first-party applications, a login confirmation prompt may still appear when the application uses a non-verifiable callback URI (such as `localhost` or a custom URI scheme). This protects users against application impersonation on the same device. To learn more, read [Measures Against Application Impersonation](/docs/secure/security-guidance/measures-against-app-impersonation).
</Callout>

## Revoke consent

To revoke a user's consent for a specific application:

1. Navigate to **Auth0 Dashboard > User Management > Users**.
2. Select the user.
3. Select the **Authorized Applications** tab.
4. Select **Revoke** next to the application.

## Password-based flows

When using the [Resource Owner Password Flow](/docs/get-started/authentication-and-authorization-flow/resource-owner-password-flow), no consent dialog is involved because the user directly provides their password to the application, which is equivalent to granting the application full access to the user's account.

## Force consent

To force users to provide consent on every login (even if they have an existing grant), include `prompt=consent` in the `/authorize` request:

```http wrap lines theme={null}
GET /authorize?
  client_id=tpc_THIRD_PARTY_CLIENT_ID
  &redirect_uri=https://partner.example.com/callback
  &response_type=code
  &scope=read:posts write:posts
  &audience=https://social.example.com
  &prompt=consent
  &code_challenge=CODE_CHALLENGE
  &code_challenge_method=S256
  &state=STATE_VALUE
```

## Learn more

* [Third-Party Applications](/docs/get-started/applications/third-party-applications)
* [First-Party and Third-Party Applications](/docs/get-started/applications/first-party-and-third-party-applications)
* [Security Controls for Third-Party Applications](/docs/get-started/applications/third-party-applications/security-controls)
* [Application Grant Types](/docs/get-started/applications/application-grant-types)
* [Measures Against Application Impersonation](/docs/secure/security-guidance/measures-against-app-impersonation)
