# OAuth authentication

> The OAuth 2.0 authorization code flow a public Shoplazza App uses to get an Access Token: parameters, each step, token refresh, and storage requirements.

# OAuth Authentication

Before a public app can call the Shoplazza Open API, it must first obtain an **Access Token** granted by the store. Shoplazza uses the OAuth 2.0 Authorization Code flow to issue tokens.

:::note
**Page purpose**: Reference layer — clearly explain the parameters, flow, and token lifecycle involved in OAuth.

**If you want to follow step-by-step to get your app running from scratch**: go to the [Build an App with Node.js and Express](/docs/app/getting-started/create-public-app/develop-app) tutorial.
:::

## Flow Overview

```
Merchant clicks Add App
        │
        ▼
① Shoplazza backend calls your configured App URL (with hmac + shop + store_id)
        │
        ▼
② Your app validates hmac → generates state → redirects to Shoplazza authorization page
        │
        ▼
③ Merchant agrees to your scope request on the Shoplazza authorization page
        │
        ▼
④ Shoplazza calls back your redirect_uri (with code + state + hmac)
        │
        ▼
⑤ Your app validates state, hmac → exchanges code for access_token
        │
        ▼
⑥ Persist (shop, access_token, refresh_token, expires_at)
        │
        ▼
   Call Open API, Header: `Access-Token: <access_token>`
```

The following diagram shows the complete OAuth authorization interaction flow:

## Key Parameters

| Parameter | Source | Description |
|---|---|---|
| `client_id` | Partner Center | App unique identifier, public |
| `client_secret` | Partner Center | App secret, **never expose it to the frontend** |
| `redirect_uri` | Your configured callback URL | Must match the one configured in the Partner Center exactly |
| `scope` | The permissions you declare | Comma or space-separated list of scopes |
| `state` | Random string you generate | Anti-CSRF, returned unchanged in the callback, must be validated |
| `hmac` | Shoplazza returns it in the query | Anti-request-forgery, must be validated, see [HMAC Signature Verification](/docs/app/building-blocks/authentication/signature-verification) for algorithm |
| `code` | Returned in Shoplazza callback | Authorization Code, **can only be used once** |
| `access_token` | Returned by the token endpoint | Credential for API calls, sent in the `Access-Token` header |
| `refresh_token` | Returned by the token endpoint | Used to refresh the access_token |
| `expires_at` | Returned by the token endpoint | Access token expiry timestamp (seconds) |

## Key Steps

### ② Validate Parameters and Redirect to Authorization

After the merchant clicks **Add App** from the Shoplazza App Store, Shoplazza calls the App URL you configured in the Partner Center. The URL looks like:

```
https://your-app.example.com/auth/install?hmac=<hmac>&install_from=app_store&shop=<store_name>.myshoplaza.com&store_id=<store_id>
```

Your app must:

1. Validate `hmac` (see [HMAC Signature Verification](/docs/app/building-blocks/authentication/signature-verification))
2. Validate that `shop` ends with `.myshoplaza.com`
3. Generate a random `state` and persist it (to compare in the callback)
4. Redirect the merchant to the Shoplazza authorization page:

```
https://<shop>/admin/oauth/authorize
  ?client_id=<client_id>
  &scope=<scopes>
  &redirect_uri=<redirect_uri>
  &response_type=code
  &state=<state>
```

On the Shoplazza authorization page, the merchant will see a permission (scope) confirmation screen:

### ④ ⑤ Callback and Token Exchange

After the merchant clicks install and confirms on the authorization page:

After the merchant authorizes, Shoplazza calls back your `redirect_uri`:

```
https://your-app.example.com/auth/callback?code=<code>&shop=<shop>&state=<state>&hmac=<hmac>
```

Validation order:

1. Validate `hmac`
2. Validate that `state` matches the one generated in step ② (anti-CSRF)
3. Validate that `shop` is legitimate

After passing validation, exchange the code for an access_token with the store's token endpoint:

```http
POST https://<shop>/admin/oauth/token
Content-Type: application/json

{
  "client_id": "<client_id>",
  "client_secret": "<client_secret>",
  "code": "<code>",
  "grant_type": "authorization_code",
  "redirect_uri": "<redirect_uri>"
}
```

Successful response:

```json
{
  "token_type": "Bearer",
  "expires_at": 1550546245,
  "access_token": "<access_token>",
  "refresh_token": "<refresh_token>",
  "store_id": "2",
  "store_name": "xiong1889"
}
```

**Field descriptions:**

- `expires_at`: Access token expiry timestamp (seconds)
- `refresh_token`: Used to refresh the access_token
- `store_id` / `store_name`: Store identifiers

You can also use the Shoplazza OAuth SDK (Go) to exchange the token:

```go
import (
  co "github.com/shoplazza-os/oauth-sdk-go"
  "github.com/shoplazza-os/oauth-sdk-go/shoplazza"
)

oauth := &co.Config{
                ClientID:     "s1Ip1WxpoEAHtPPzGiP2rK2Az-P07Nie7V97hRKigl4",
                ClientSecret: "{YOUR_CLIENT_SECRET}",
                Endpoint:     shoplazza.Endpoint,
                RedirectURI:  "https://3830-43-230-206-233.ngrok.io/oauth_sdk/redirect_uri/",
                Scopes:       []string{"read_shop"},
        }
token, err := oauth.Exchange(context.Background(),"xxx.myshoplaza.com", "code"))
```

:::info Token validity
Both the `access_token` and `refresh_token` are valid for **1 year**. Before the `access_token` reaches its `expires_at`, use the `refresh_token` to obtain a new token (see "Refresh Access Token" below).
:::

### ⑥ Persist and Make API Calls

Store `(shop, access_token, refresh_token, expires_at)` in your data store (database, Redis, etc.). When calling Open APIs, include the token in the Header:

```http
GET https://<shop>/openapi/2022-01/customers HTTP/1.1
Access-Token: <access_token>
```

## Refresh Access Token

When the access_token approaches its `expires_at`, use the refresh_token to get a new one:

```http
POST https://<shop>/admin/oauth/token
Content-Type: application/json

{
  "client_id": "<client_id>",
  "client_secret": "<client_secret>",
  "refresh_token": "<refresh_token>",
  "grant_type": "refresh_token",
  "redirect_uri": "<redirect_uri>"
}
```

The response fields are the same as in the initial token exchange.

You can also use the Shoplazza OAuth SDK (Go) to refresh the token:

```go
import (
  co "github.com/shoplazza-os/oauth-sdk-go"
  "github.com/shoplazza-os/oauth-sdk-go/shoplazza"
)

oauth := &co.Config{
                ClientID:     "s1Ip1WxpoEAHtPPzGiP2rK2Az-P07Nie7V97hRKigl4",
                ClientSecret: "{YOUR_CLIENT_SECRET}",
                Endpoint:     shoplazza.Endpoint,
                RedirectURI:  "https://3830-43-230-206-233.ngrok.io/oauth_sdk/redirect_uri/",
                Scopes:       []string{"read_shop"},
        }
token, err := oauth.RefreshToken(context.Background(), "xxx.myshoplaza.com", "refresh token")
```

## Security Requirements

- `client_secret` and `access_token` must always be stored server-side, never sent to the browser
- Every callback must validate `hmac` and `state`
- `code` is one-time use; reusing it will fail
- HTTPS must be used
- **SSRF protection**: Validate that the `shop` parameter is a legitimate `*.myshoplaza.com` domain
- **CSRF protection**: Generate a random `state` when initiating authorization, verify and consume it in the callback
- **Timing attack protection**: Use `crypto.timingSafeEqual` for HMAC comparison

## Token Storage Convention

The example stores `access_token` in SQLite using the store domain as the primary key (table `shop_tokens`: `shop_domain` as primary key, `access_token`, `updated_time`). When the store accesses the app again, it reads directly from the database without requiring re-authorization. For production, we recommend:

- Switching to a dedicated database like PostgreSQL or MySQL
- Configuring the database path via an environment variable (e.g., `DB_PATH`) to avoid relative path issues in different deployment environments
- Using `require("sqlite3")` instead of `.verbose()` to prevent extra debug logs from affecting performance

## Best Practices

### Environment Variable Management

- **Development**: Use a `.env` file
- **Production**: Use your cloud platform's environment variable feature or a secret management service (e.g., AWS Secrets Manager, Azure Key Vault, Alibaba Cloud KMS)
- Never hardcode sensitive information in your project
- Add `.env` to `.gitignore`

```javascript
const CLIENT_SECRET = process.env.CLIENT_SECRET;
```

### HMAC Verification

All requests from Shoplazza should validate the HMAC signature:

```javascript
app.get("/auth", hmacValidator, (req, res) => {
  // Only requests that pass verification execute this code
});
```

### Project Structure

For production projects, we recommend a front-end/back-end separated structure:

```text
shoplazza-app-demo/
├── server/
│   ├── routes/
│   ├── utils/
│   ├── database/
│   └── index.js
├── src/
│   ├── components/
│   ├── pages/
│   └── main.js
├── dist/
├── package.json
└── .env
```

## Reference Implementations

- Complete Node.js + Express example: [Build an App with Node.js and Express](/docs/app/getting-started/create-public-app/develop-app)
- SDKs for various languages: [API Client Libraries](/docs/app/developer-tools/api-libraries)
- List of scopes to request: [Access Scopes](/docs/app/building-blocks/authentication/access-scopes)
