How-To: Direct Authentication
We would suggest reading the general authentication information and the standard OAuth guide 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:
- Identify which Shopfront vendor initiated the flow.
- Run your signup or authorisation process.
- (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 equalshopfrontfor 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
// 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);
});
<?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);
});
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
// 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);
});
<?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);
});