# How To / Direct Authentication

# How-To: Direct Authentication

> We would suggest reading the [general authentication information](/documentation/General/Authentication) and the [standard OAuth guide](/documentation/How-To/Authenticate)
> before reading this document.

In order to become a member of Shopfront's Partner Program, you'll need to support a concept we call 
"direct authentication", which is used when your integration appears in the Shopfront marketplace and
allows users to be redirected to your site for them to sign up or to quickly complete authorisation.

## Getting Started

When a user clicks the "Integrate" button for your app inside Shopfront (Menu > Setup > Integrations),
Shopfront performs a `GET` redirect to a URL you provide during the partner program sign-up process.

This allows you to:

1. Identify which Shopfront vendor initiated the flow.
2. Run your signup or authorisation process.
3. (optionally) Redirect the user back to Shopfront when complete.

## Handling the Marketplace Redirect

Shopfront will redirect the user to your integration URL using the following format:

`https://[your-url]?vendor=[subdomain]&setup=shopfront&redirect=[url-to-redirect-once-done]`

The `redirect` parameter will be URL-encoded and should be treated as an opaque URL. You can store it and
use it later to return the user to Shopfront.

You should expect these query parameters:

- `vendor`: The vendor subdomain initiating the integration.
- `setup`: Will equal `shopfront` for this flow.
- `redirect`: The Shopfront URL to send the user back to after your flow is complete.

In the future, additional query parameters may be added.

**Handling the Marketplace Redirect**

```javascript
// This is a server-side example for handling the marketplace redirect.

globalThis.shopfrontDirectAuth = {
    states: {},
    redirects: {},
};

const createState = (vendor, redirectUrl) => {
    const state = btoa(`${vendor}:${Date.now()}`);
    globalThis.shopfrontDirectAuth.states[state] = vendor;
    globalThis.shopfrontDirectAuth.redirects[state] = redirectUrl;

    // Clean up after 10 minutes.
    setTimeout(() => {
        delete globalThis.shopfrontDirectAuth.states[state];
        delete globalThis.shopfrontDirectAuth.redirects[state];
    }, 10 * 60 * 1000);

    return state;
};

routes.get("/shopfront/direct-auth", (request, response) => {
    const vendor = request.queryString.get("vendor");
    const redirectUrl = request.queryString.get("redirect");

    const state = createState(vendor, redirectUrl);

    // Instead of redirecting straight back to Shopfront here, you may want to show a marketing page or have them
    // authenticate with your application first
    const url = new URL(`https://${vendor}.onshopfront.com/oauth/authorize`);
    url.searchParams.append("client_id", process.env.SHOPFRONT_CLIENT_ID);
    url.searchParams.append("redirect_uri", process.env.SHOPFRONT_REDIRECT_URI);
    url.searchParams.append("response_type", "code");
    url.searchParams.append("state", state);
    url.searchParams.append("scope", "modify_integrations create_webhooks");

    return response.redirect(url);
});
```

**Handling the Marketplace Redirect**

```php
<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

function storeDirectAuthState(string $state, string $vendor, string $redirectUrl): void {
    $storagePath = "/tmp/shopfrontDirectAuth/" . $state;
    if(!is_dir(dirname($storagePath))) {
        mkdir(dirname($storagePath), 0777, true);
    }

    file_put_contents($storagePath, json_encode([
        "vendor" => $vendor,
        "redirect" => $redirectUrl,
        "expiry" => time() + (10 * 60),
    ]));
}

function createDirectAuthState(string $vendor, string $redirectUrl): string {
    $state = base64_encode($vendor . time());
    storeDirectAuthState($state, $vendor, $redirectUrl);

    return $state;
}

Route::get("/shopfront/direct-auth", function(ServerRequestInterface $request): ResponseInterface {
    $parameters = $request->getQueryParams();
    $vendor = $parameters["vendor"];
    $redirectUrl = $parameters["redirect"];

    $state = createDirectAuthState($vendor, $redirectUrl);

    // Instead of redirecting straight back to Shopfront here, you may want to show a marketing page or have them
    // authenticate with your application first
    $urlParameters = http_build_query([
        "client_id" => env("SHOPFRONT_CLIENT_ID"),
        "redirect_uri" => env("SHOPFRONT_REDIRECT_URI"),
        "response_type" => "code",
        "state" => $state,
        "scope" => "modify_integrations create_webhooks",
    ]);

    $url = "https://" . $vendor . ".onshopfront.com/oauth/authorize?" . $urlParameters;

    return Response::redirect($url);
});
```

## Authorising the User

Once the user lands on your site, you can prompt them to sign up or log in and then begin the
standard OAuth flow with Shopfront (see the [Authenticate guide](/documentation/How-To/Authenticate)).

We recommend using the `vendor` parameter to direct the user to the vendor-specific OAuth endpoint:

`https://[vendor].onshopfront.com/oauth/authorize`

When going through the partner program onboarding process, you'll need to demo to us how this interaction works and how
a user flows from Shopfront through to your app and back to Shopfront for authentication. Care should be taken with 
this flow as it's the most likely way users will first interact with your application from Shopfront.

## Returning to Shopfront

After you exchange the OAuth `code` for an access token, you may wish to redirect the user to the `redirect` URL
provided by Shopfront at the start of the flow. This will allow the user to continue where they left off before leaving
Shopfront to integrate with your application.

If the user cancels or fails authorisation on your side, you can still return them to the
`redirect` URL so they can resume where they left off.

**Returning to Shopfront**

```javascript
// This is a server-side example for handling the OAuth callback.

routes.get("/shopfront/oauth/callback", async (request, response) => {
    const state = request.queryString.get("state");
    const code = request.queryString.get("code");

    const redirectUrl = globalThis.shopfrontDirectAuth.redirects[state];
    const vendor = globalThis.shopfrontDirectAuth.states[state];

    const tokenResponse = await fetch("https://onshopfront.com/oauth/token", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            client_id: process.env.SHOPFRONT_CLIENT_ID,
            client_secret: process.env.SHOPFRONT_CLIENT_SECRET,
            redirect_uri: process.env.SHOPFRONT_REDIRECT_URI,
            code,
            grant_type: "authorization_code",
        }),
    });

    const tokenBody = await tokenResponse.json();
    await storeShopfrontTokens(vendor, tokenBody);

    return response.redirect(redirectUrl);
});
```

**Returning to Shopfront**

```php
<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

function readDirectAuthState(string $state): ?object {
    $storagePath = "/tmp/shopfrontDirectAuth/" . $state;
    if(!file_exists($storagePath)) {
        return null;
    }

    $data = json_decode(file_get_contents($storagePath));
    if($data->expiry <= time()) {
        return null;
    }

    return $data;
}

Route::get("/shopfront/oauth/callback", function(ServerRequestInterface $request): ResponseInterface {
    $parameters = $request->getQueryParams();
    $state = $parameters["state"];
    $code = $parameters["code"];

    $stored = readDirectAuthState($state);
    if(!$stored) {
        return Response::json(["error" => "invalid_state"], 400);
    }

    $tokenBody = httpPostJson("https://onshopfront.com/oauth/token", [
        "client_id" => env("SHOPFRONT_CLIENT_ID"),
        "client_secret" => env("SHOPFRONT_CLIENT_SECRET"),
        "redirect_uri" => env("SHOPFRONT_REDIRECT_URI"),
        "code" => $code,
        "grant_type" => "authorization_code",
    ]);

    storeShopfrontTokens($stored->vendor, $tokenBody);

    return Response::redirect($stored->redirect);
});
```