---
title: "بررسی توکن کاربر روی سرور شما"
description: "توکنی را که اپ به سرور خودتان می‌فرستد بررسی کنید: امضای ES256 با کلیدهای عمومی ابرووا (JWKS در /.well-known/jwks.json)، صادرکننده، مخاطب، زمان انقضا و پروژه‌ی شما. همان مدل verifyIdToken در Firebase، با هر کتابخانه‌ی استاندارد JWT در Node.js، PHP، پایتون یا Go."
product: auth
language: fa
url: https://abrova.ir/docs/auth/verify-tokens
updated: 2026-11-14
---

# بررسی توکن کاربر روی سرور شما

توکنی را که اپ به سرور خودتان می‌فرستد بررسی کنید: امضای ES256 با کلیدهای عمومی ابرووا (JWKS در /.well-known/jwks.json)، صادرکننده، مخاطب، زمان انقضا و پروژه‌ی شما. همان مدل verifyIdToken در Firebase، با هر کتابخانه‌ی استاندارد JWT در Node.js، PHP، پایتون یا Go.

> **کی به این نیاز دارید**
>
> اپ شما **API خودتان** را صدا می‌زند، مثلاً برای ثبت سفارش. سرور شما باید بداند چه کسی درخواست داده. اپ توکن کاربر واردشده را می‌فرستد و سرور شما آن را با کلیدهای عمومی ابرووا بررسی می‌کند. نه برای هر درخواست تماسی با ابرووا لازم است، نه رمز مشترکی: همان مدل `verifyIdToken` در Firebase.

## چطور کار می‌کند

هر توکن کاربر ابرووا یک JWT است که با **ES256** امضا شده. ابرووا کلیدهای عمومی بررسی امضا را به شکل استاندارد **JWKS** (RFC 7517) منتشر می‌کند:

```text
https://auth.abrova.ir/.well-known/jwks.json
```

آن‌جا فقط کلید عمومی هست. کلید خصوصی هیچ‌وقت از ابرووا بیرون نمی‌رود، پس کسی نمی‌تواند توکن بسازد، ولی همه می‌توانند توکن را بررسی کنند. هر کلید یک شناسه (`kid`) دارد و هر توکن `kid` کلیدی را که امضایش کرده می‌گوید. وقتی ابرووا کلیدش را عوض می‌کند، کلید تازه پیش از آن‌که توکنی با آن امضا شود منتشر می‌شود و کلید قبلی دست‌کم یک روز بعد هم منتشر می‌ماند؛ پس سروری که کلیدها را ۱۰ دقیقه نگه می‌دارد هیچ کلیدی را از دست نمی‌دهد.

کلیدها از داخل ایران ارائه می‌شوند، پس سرور شما هنگام قطع اینترنت بین‌الملل هم می‌تواند توکن‌ها را بررسی کند.

## مرحله 1: فرستادن توکن از اپ

توکن فعلی را در اپ بخوانید و در سرآیند `Authorization` بفرستید:

| پلتفرم | خواندن توکن |
| --- | --- |
| `Flutter` | await auth.getToken() |
| `iOS` | try await auth.getAccessToken() |
| `Android` | auth.getAccessToken() |
| `Web، React Native` | await auth.getToken() |

```http
GET /api/orders
Authorization: Bearer eyJhbGciOiJFUzI1NiIs...
```

SDK توکن را پیش از انقضا تمدید می‌کند، پس آن را برای هر درخواست بخوانید و نسخه‌ای نگه ندارید. API خودتان را همیشه با HTTPS صدا بزنید.

## مرحله 2: بررسی روی سرور

> **پروژه را بررسی کنید، نه فقط امضا را**
>
> یک کلید توکن‌های **همه‌ی** پروژه‌های ابرووا را امضا می‌کند. توکنی با امضای درست هم ممکن است مال کاربر اپ دیگری باشد. همیشه بررسی کنید که `pid` شناسه‌ی پروژه‌ی **شما** باشد (کنسول › تنظیمات پروژه › عمومی).

### Node.js: SDK مدیریتی

با [@abrova/auth-admin](https://abrova.ir/docs/auth/admin-sdk) نسخه‌ی ۰.۲.۰ یا بالاتر، یک فراخوانی همه‌ی بررسی‌های این صفحه را انجام می‌دهد:

```ts
import { initAdmin } from '@abrova/auth-admin';

const auth = initAdmin(); // ABROVA_PROJECT_ID، ABROVA_API_KEY و ABROVA_SERVER_SECRET را می‌خواند

app.use(async (req, res, next) => {
  const token = req.headers.authorization?.replace(/^Bearer /, '');
  try {
    req.user = await auth.verifyIdToken(token);
    next();
  } catch {
    res.status(401).json({ error: 'Sign in again' });
  }
});

// req.user.uid، req.user.role (یک claim سفارشی)، req.user.amr (روش ورود)
```

برای کارهای حساس، مثل پرداخت یا پاک کردن داده، این را هم بررسی کنید که کاربر بعد از صدور توکن غیرفعال نشده یا از همه‌ی دستگاه‌ها خارج نشده باشد. این یک درخواست به ابرووا احراز هویت هزینه دارد:

```ts
const user = await auth.verifyIdToken(token, true); // checkRevoked: بررسی باطل شدن
```

| کد خطا | معنی |
| --- | --- |
| `INVALID_ID_TOKEN` | توکن معتبر نیست: امضای نادرست، پروژه‌ی دیگر، صادرکننده یا مخاطب نادرست. پیام خطا می‌گوید کدام. |
| `ID_TOKEN_EXPIRED` | توکن منقضی شده. اپ در فراخوانی بعدی توکن تازه می‌گیرد. |
| `ID_TOKEN_REVOKED` | با checkRevoked: کاربر بعد از صدور این توکن از همه‌ی دستگاه‌ها خارج شده. |
| `USER_DISABLED` | با checkRevoked: کاربر در کنسول غیرفعال شده. |
| `NETWORK` | کلیدهای عمومی خوانده نشد. کلیدهایی که قبلاً خوانده شده‌اند کار می‌کنند. |

### هر زبانی: یک کتابخانه‌ی استاندارد JWT

هر کتابخانه‌ی JWT که JWKS می‌خواند کار می‌کند. همه‌ی این بررسی‌ها را انجام دهید:

| بررسی | مقدار |
| --- | --- |
| `الگوریتم` | فقط ES256. هر alg دیگری، از جمله "none"، را رد کنید. |
| `کلید` | از https://auth.abrova.ir/.well-known/jwks.json با kid توکن. هرگز کلید یا نشانی کلید (jwk، jku، x5u) از خود توکن. |
| `iss` | abrova |
| `aud` | abrova_user |
| `exp` | در آینده. حداکثر ۳۰ ثانیه اختلاف ساعت را بپذیرید. |
| `pid` | شناسه‌ی پروژه‌ی شما. |
| `sub` | شناسه‌ی کاربر: ru_... برای کاربر ابرووا احراز هویت، یا شناسه‌ای که سرور شما هنگام درخواست توکن داده. |

**Node.js** با [jose](https://github.com/panva/jose):

```ts
import { createRemoteJWKSet, jwtVerify } from 'jose';

const JWKS = createRemoteJWKSet(new URL('https://auth.abrova.ir/.well-known/jwks.json'));

export async function verifyUser(token: string) {
  const { payload } = await jwtVerify(token, JWKS, {
    algorithms: ['ES256'],
    issuer: 'abrova',
    audience: 'abrova_user',
    clockTolerance: 30,
  });
  if (payload.pid !== process.env.ABROVA_PROJECT_ID) throw new Error('Token of another project');
  return payload; // payload.sub شناسه‌ی کاربر است
}
```

**PHP (لاراول)** با [firebase/php-jwt](https://github.com/firebase/php-jwt) نسخه‌ی ۶ یا بالاتر:

```php
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

function verifyUser(string $token): object
{
    $jwks = Cache::remember('abrova_jwks', 600, fn () =>
        Http::timeout(5)->get('https://auth.abrova.ir/.well-known/jwks.json')->throw()->json());

    JWT::$leeway = 30;
    $claims = JWT::decode($token, JWK::parseKeySet($jwks, 'ES256'));

    if ($claims->iss !== 'abrova' || $claims->aud !== 'abrova_user' || $claims->pid !== env('ABROVA_PROJECT_ID')) {
        throw new UnexpectedValueException('Not a token of this project');
    }
    return $claims; // $claims->sub شناسه‌ی کاربر است
}
```

`JWT::decode` امضا را با کلیدی که `kid` توکن می‌گوید، و زمان انقضا را بررسی می‌کند. وقتی ابرووا کلید تازه‌ای اضافه می‌کند، توکنی با `kid` ناشناخته تا تازه شدن کش (حداکثر ۱۰ دقیقه) رد می‌شود؛ در این حالت کش را پاک کنید و یک بار دوباره امتحان کنید.

**پایتون** با [PyJWT](https://pyjwt.readthedocs.io) (`pip install "pyjwt[crypto]"`):

```python
import os
import jwt

jwks = jwt.PyJWKClient("https://auth.abrova.ir/.well-known/jwks.json", lifespan=600)

def verify_user(token: str) -> dict:
    key = jwks.get_signing_key_from_jwt(token).key
    claims = jwt.decode(token, key, algorithms=["ES256"], issuer="abrova", audience="abrova_user", leeway=30)
    if claims.get("pid") != os.environ["ABROVA_PROJECT_ID"]:
        raise jwt.InvalidTokenError("Token of another project")
    return claims  # claims["sub"] شناسه‌ی کاربر است
```

**Go** با [golang-jwt](https://github.com/golang-jwt/jwt) نسخه‌ی ۵ و [keyfunc](https://github.com/MicahParks/keyfunc) نسخه‌ی ۳:

```go
var jwks keyfunc.Keyfunc

func init() {
	var err error
	jwks, err = keyfunc.NewDefault([]string{"https://auth.abrova.ir/.well-known/jwks.json"})
	if err != nil {
		log.Fatal(err)
	}
}

func verifyUser(token string) (jwt.MapClaims, error) {
	claims := jwt.MapClaims{}
	_, err := jwt.ParseWithClaims(token, claims, jwks.Keyfunc,
		jwt.WithValidMethods([]string{"ES256"}),
		jwt.WithIssuer("abrova"),
		jwt.WithAudience("abrova_user"),
		jwt.WithExpirationRequired(),
		jwt.WithLeeway(30*time.Second))
	if err != nil {
		return nil, err
	}
	if claims["pid"] != os.Getenv("ABROVA_PROJECT_ID") {
		return nil, errors.New("token of another project")
	}
	return claims, nil // claims["sub"] شناسه‌ی کاربر است
}
```

## مرحله 3: استفاده از claimها

| Claim | چیست |
| --- | --- |
| `sub` | شناسه‌ی کاربر. |
| `pid` | پروژه‌ای که توکن برای آن است. |
| `iss، aud` | abrova و abrova_user. |
| `iat، exp` | زمان صدور و زمان انقضای توکن (به ثانیه). هر توکن یک ساعت عمر دارد. |
| `auth_time` | زمان ورود کاربر. |
| `amr` | روش ورود کاربر، مثلاً ["google"]. فقط در توکن‌های ابرووا احراز هویت. |
| `claimهای سفارشی شما` | در سطح اول توکن، مثلاً role یا plan. ببینید: claimهای سفارشی. |

دسترسی را روی سرور خودتان از روی `sub` و [claimهای سفارشی](https://abrova.ir/docs/auth/custom-claims) تصمیم بگیرید، هرگز از چیز دیگری که اپ می‌فرستد.

## نکته‌ها

- **باطل کردن.** توکن تا زمان انقضا، حداکثر یک ساعت، معتبر می‌ماند، حتی بعد از خروج کاربر از همه‌ی دستگاه‌ها. در Firebase هم همین‌طور است. هرجا مهم است، باطل شدن را بررسی کنید (`checkRevoked` در SDK مدیریتی، یا `tokensValidAfterTime` کاربر از `getUser`).
- **توکن‌هایی که سرور شما درخواست کرده** (`POST /users/token` با کلید سرور) هم همین‌طور بررسی می‌شوند. `sub` آن‌ها همان شناسه‌ای است که داده‌اید.
- **هرگز در اپ بررسی نکنید.** اپ claimهای خودش را با `getTokenResult()` می‌خواند تا صفحه‌ها را انتخاب کند. فقط بررسی روی سرور شما قابل اعتماد است.
- **توکن‌ها را لاگ نکنید.** هر کسی که توکن را داشته باشد تا زمان انقضا می‌تواند جای کاربر کار کند.
