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

# Access Tokens: Post-Purchase Gated Content Magic Links

> How ContentAccessToken generates UUID magic links for post-purchase access to gated CMS pages, with guest support, 90-day expiry, and session redemption.

`ContentAccessToken` is Site Store Pro's mechanism for granting secure, time-limited access to gated CMS pages after a customer completes a purchase. Instead of requiring every buyer to register an account or stay logged in, the system generates a unique UUID magic link and emails it to the customer. When they visit the link, they are automatically granted access to the protected content — no password required.

<Tip>
  Access tokens are the foundation for building membership portals and digital product delivery flows on Site Store Pro. Gate any CMS page behind a product purchase, then use the magic link email to deliver the content URL directly to paying customers. Because guest purchasers are fully supported, you can sell access to digital content without requiring account creation at checkout.
</Tip>

***

## Token Generation

Tokens are created automatically when an order reaches the `completed` status. You do not call this manually for standard flows — the order lifecycle triggers it.

```text theme={null}
Order Completed
  → ContentAccessToken::generateOrRefresh()
  → Stores record in content_access_tokens table
  → Sends email containing: /content-access/{uuid}
```

`generateOrRefresh()` either creates a new token or refreshes an existing one (resetting `expires_at` to 90 days from now). Admins can also trigger a refresh manually by clicking **Send Download Reminder** or using the resend email action on the order — both call `generateOrRefresh()` and dispatch a fresh link to the customer.

***

## Redemption URL

```text theme={null}
GET /content-access/{uuid}
```

When a customer visits their magic link, the platform performs the following redemption steps:

<Steps>
  <Step title="Validate the token">
    The system looks up the `uuid` in the `content_access_tokens` table and confirms it exists and has not passed its `expires_at` timestamp.
  </Step>

  <Step title="Push purchase proof to session">
    On a valid token, the system pushes the associated `product_id` into `session('verified_purchased_products')`. This session key is what gated CMS pages check to determine whether to display their content.
  </Step>

  <Step title="Redirect to content">
    The customer is redirected to the `completion_redirect` URL configured on the product. This is typically the gated CMS page URL itself, which the customer can now view because the session is populated.
  </Step>
</Steps>

***

## Token Expiry and Refresh

By default, every token expires **90 days** from its generation date. The `expires_at` column stores this timestamp.

| Event                                   | Behavior                                                  |
| --------------------------------------- | --------------------------------------------------------- |
| Order completed                         | New token generated with `expires_at = now() + 90 days`   |
| Admin clicks **Send Download Reminder** | Token refreshed — `expires_at` reset to `now() + 90 days` |
| Admin uses resend email action          | Same as above — `generateOrRefresh()` called              |
| Token visited after expiry              | Access denied; customer must request a new link           |

<Note>
  Refreshing a token via the admin resend action does not invalidate the previous UUID — it updates the same record. The old link URL continues to work with the new expiry date.
</Note>

***

## Guest Access

Customers who check out as guests — without creating an account — are fully supported. Because the magic link redemption flow writes purchase proof to the session (not to the user account), a guest purchaser who clicks their emailed link receives the same seamless access as a logged-in customer.

This design means you do not need to force account creation at checkout to sell gated content. The token carries all the proof needed.

***

## CMS Page Gating Flow

When a CMS page has `required_product_id` set, every visitor goes through the following access check:

<Steps>
  <Step title="Visitor arrives at the gated page">
    The CMS page controller reads the `required_product_id` configured on the page record.
  </Step>

  <Step title="Access check — three conditions evaluated in order">
    The system grants access if **any one** of the following is true:

    <CardGroup cols={1}>
      <Card title="Condition A — Logged-in verified purchase" icon="circle-check">
        The visitor is authenticated and their account has a verified purchase of the required product on record.
      </Card>

      <Card title="Condition B — Session purchase proof" icon="circle-check">
        The visitor's session contains `verified_purchased_products` with the required `product_id` — set by a previous magic link redemption in this browser session.
      </Card>

      <Card title="Condition C — Valid magic link UUID in URL" icon="circle-check">
        A valid, non-expired UUID token is present in the URL and resolves to the required product. The token is redeemed on the spot, populating the session and redirecting.
      </Card>
    </CardGroup>
  </Step>

  <Step title="Access denied — show purchase gate">
    If none of the three conditions are met, the system displays a lock screen or purchase gate prompting the visitor to buy the product.
  </Step>
</Steps>

***

## Database Schema

The `content_access_tokens` table stores one record per customer–product pairing.

```sql theme={null}
CREATE TABLE content_access_tokens (
    id         BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    uuid       VARCHAR(36)     NOT NULL UNIQUE,  -- The magic link token
    user_id    BIGINT UNSIGNED NOT NULL,          -- FK → users.id (purchasing customer)
    product_id BIGINT UNSIGNED NOT NULL,          -- FK → products.id (purchased product)
    expires_at TIMESTAMP       NOT NULL,          -- 90 days from generation/refresh
    created_at TIMESTAMP       NULL,
    updated_at TIMESTAMP       NULL
);
```

<ResponseField name="uuid" type="string">
  A cryptographically secure random UUID. This value is the token embedded in the magic link URL: `/content-access/{uuid}`.
</ResponseField>

<ResponseField name="user_id" type="integer">
  Foreign key to `users.id`. References the purchasing customer's account. For guest purchases, this may reference a guest user record created at checkout.
</ResponseField>

<ResponseField name="product_id" type="integer">
  Foreign key to `products.id`. Identifies which product purchase this token represents access proof for.
</ResponseField>

<ResponseField name="expires_at" type="timestamp">
  The UTC timestamp at which this token becomes invalid. Defaults to 90 days after creation. Reset to `now() + 90 days` on every `generateOrRefresh()` call.
</ResponseField>
