--- url: https://docs.youcan.shop/apps/external_app/auth.md --- # 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](/apps/embedded_app/auth) instead. ## Core Concepts | Term | Description | |---|---| | **Authorization Code** (`code`) | A one-time code in the launch URL. Directly exchangeable for an access token. Short-lived — exchange it immediately. | | **Access Token** | A long-lived bearer token for the YouCan Store Admin API. | | **HMAC** | A 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 ```mermaid sequenceDiagram actor seller participant SA as Seller Area participant Browser participant Server as Your App Server participant API as YouCan API seller->>SA: Opens app SA->>SA: Build URL with code=CODE, embedded=0, HMAC SA->>Browser: Redirect to https://app.com/?code=CODE&hmac=SIG Browser->>Server: GET / (with code in URL) Server->>Server: Verify HMAC Server->>API: POST /oauth/token (authorization_code) API-->>Server: access_token Server->>Server: Persist session + access_token Server-->>Browser: Render app ```