Skip to content

External App Authentication

External apps authenticate via an authorization code flow. When a seller opens your app, YouCan redirects them to your URL with a one-time ?code= parameter, which your server exchanges for a store access token.

If you're building an embedded app that runs inside the Seller Area iframe, see Embedded App Authentication instead.

Core Concepts

TermDescription
Authorization Code (code)A one-time code in the launch URL. Directly exchangeable for an access token. Short-lived — exchange it immediately.
Access TokenA long-lived bearer token for the YouCan Store Admin API.
HMACA SHA-256 signature on the launch URL, proving it came from YouCan.

The Launch URL

When a seller opens your external app, the Seller Area redirects them to:

https://your-app.com/?timestamp=...&code=AUTH_CODE&state=STATE&store=my-store&seller=ID&locale=en&embedded=0&hmac=SIG

Step 1 — HMAC Verification

Always verify the HMAC before doing anything else. An invalid HMAC means the request didn't come from YouCan.

  1. Take all query params from the URL except hmac.
  2. Build a query string (in the same order as received).
  3. Compute HMAC-SHA256(query_string, CLIENT_SECRET).
  4. Compare using constant-time comparison.

WARNING

Never use == or === to compare signatures. Use constant-time comparison to prevent timing attacks.

js
const crypto = require('crypto');

function verifyHmac(queryString, clientSecret) {
  const params = new URLSearchParams(queryString);
  const received = params.get('hmac');
  params.delete('hmac');

  const message = params.toString();
  const expected = crypto.createHmac('sha256', clientSecret).update(message).digest('hex');

  const a = Buffer.from(received ?? '', 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Step 2 — Exchange the Code

Exchange the ?code= for an access token server-side, before rendering the page. The code is one-time use and short-lived.

js
// Express route handler
app.get('/', async (req, res) => {
  const { code, store, hmac } = req.query;

  if (!verifyHmac(req.url.split('?')[1], process.env.YOUCAN_API_SECRET)) {
    return res.status(403).send('Forbidden');
  }

  const tokenRes = await fetch('https://api.youcan.shop/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: process.env.YOUCAN_API_KEY,
      client_secret: process.env.YOUCAN_API_SECRET,
      code,
    }),
  });

  const { access_token } = await tokenRes.json();

  await db.session.upsert({ store, accessToken: access_token });

  res.redirect('/dashboard');
});

WARNING

The code is one-time use and short-lived. Exchange it immediately — never store it.

The exchange returns:

json
{ "access_token": "...", "expires_in": 86400 }

Full Flow Diagram