Skip to content

External App Authentication ​

Last updated View as Markdown

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 ​