راستش را بخواهید، احراز هویت همیشه یکی از آن بخشهایی بوده که توسعهدهندههای React Native را به دردسر میاندازد. در وب همهچیز ساده است — یک کوکی HTTP-only تنظیم میکنید و خلاص. ولی در موبایل ماجرا فرق میکند: توکنها باید در فضاهای رمزنگاریشدهی پلتفرم (یعنی Keychain در iOS و Keystore در Android) ذخیره شوند، Session باید بین Restartهای اپ پایدار بماند و حالت لاگین باید Real-time در سراسر اپ همگام شود.
خوشبختانه ماجرای ۲۰۲۶ کاملاً عوض شده. Clerk در ۹ مارس امسال نسخهی ۳.۱ از @clerk/expo را منتشر کرد و برای اولین بار کامپوننتهای UI واقعاً Native را برای Expo معرفی کرد — یعنی همان چیزی که با SwiftUI در iOS و Jetpack Compose در Android رندر میشود، نه با WebView. در این مقاله میخواهیم قدمبهقدم یک سیستم احراز هویت کامل را با Clerk، Expo Router و New Architecture پیاده کنیم.
چرا Clerk + Expo Router در سال ۲۰۲۶؟
وقتی پای ساخت یک سیستم احراز هویت کامل (با OAuth، Passkey، MFA و مدیریت پروفایل) به میان میآید، انتخابها کم نیست: Firebase Auth، Auth0، Supabase، Better Auth و Clerk. خودِ من چند ماه پیش روی یک پروژهی موازی هر چهارتای اول را تست کردم و در نهایت برای اپ Expo سراغ Clerk رفتم. دلایلش؟
- کامپوننتهای Native واقعی: از مارس ۲۰۲۶، Clerk تنها Provider بزرگی است که UI رسمی با SwiftUI و Jetpack Compose برای Expo ارائه میدهد. این یعنی پایان عصر WebView برای صفحات لاگین.
- پشتیبانی از Passkey: Passkey در ۲۰۲۶ عملاً به استاندارد جدید احراز هویت موبایل تبدیل شده و Clerk به صورت Out-of-the-box از آن پشتیبانی میکند.
- Native Google Sign-In: با Credential Manager در Android و ASAuthorization در iOS، دیگر خبری از Browser Redirect نیست (و این یعنی تجربهی کاربری به مراتب بهتر).
- سازگاری مستقیم با Expo Router: ادغام طبیعی با الگوی
Stack.Protectedدر Expo Router v5. - مدیریت کامل Session: ذخیرهسازی امن توکن، Refresh خودکار و همگامسازی بین کامپوننتهای Native و JS.
اگر هنوز با مفاهیم Expo Router آشنا نیستید، پیشنهاد میکنم اول یک نگاهی به مقالهی راهنمای کامل Expo Router بیندازید تا با Routing مبتنی بر فایل گرم بگیرید.
پیشنیازها و راهاندازی پروژه
نیازمندیهای پروژه
- Node.js نسخه ۲۰ یا بالاتر (LTS فعلی در ۲۰۲۶ نسخهی ۲۲ است)
- Expo SDK 53 یا بالاتر — نسخهی ۳ Clerk حداقل به این SDK نیاز دارد
- Development Build: یادتان باشد، AuthView و کامپوننتهای Native در Expo Go کار نمیکنند
- یک حساب در
dashboard.clerk.comو یک Application جدید
ایجاد پروژه و نصب پکیجها
npx create-expo-app@latest my-auth-app
cd my-auth-app
# نصب Clerk و وابستگیهای احراز هویت
npx expo install @clerk/expo expo-secure-store expo-web-browser
# برای کامپوننتهای Native (AuthView)
npx expo install @clerk/expo-passkeys
# برای Development Build
npx expo install expo-dev-client
تنظیم متغیرهای محیطی
یک فایل .env.local در ریشهی پروژه بسازید:
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxx
این کلید را از داشبورد Clerk، بخش API Keys، کپی کنید. در ۲۰۲۶ کلیدهای جدید Clerk فرمت pk_test_ یا pk_live_ دارند.
تنظیم ClerkProvider در Expo Router
اولین کاری که باید بکنیم، پیچیدن کل اپ داخل ClerkProvider است. در Expo Router این اتفاق در app/_layout.tsx میافتد:
// app/_layout.tsx
import { ClerkProvider } from '@clerk/expo';
import { tokenCache } from '@clerk/expo/token-cache';
import { Slot } from 'expo-router';
const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY!;
if (!publishableKey) {
throw new Error(
'EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY در فایل .env تعریف نشده است'
);
}
export default function RootLayout() {
return (
<ClerkProvider
publishableKey={publishableKey}
tokenCache={tokenCache}
>
<Slot />
</ClerkProvider>
);
}
یک نکتهی مهم که خیلیها از قلم میاندازند: از نسخهی ۳ به بعد، Clerk به صورت پیشفرض از tokenCache آماده استفاده میکند که در پشت صحنه از expo-secure-store برای ذخیرهی رمزنگاریشدهی توکنها بهره میگیرد. یعنی توکنها در iOS داخل Keychain و در Android داخل EncryptedSharedPreferences مینشینند. تمام.
مدیریت دسترسی با Stack.Protected در Expo Router v5
Expo Router v5 یک API خیلی تمیز به نام Stack.Protected اضافه کرده که به صورت Declarative دسترسی به Routeها را کنترل میکند. این روش جایگزین خوبی برای الگوی قدیمی <Redirect> در Layoutهاست — و راستش، خیلی هم قابل خواندنتر است:
// app/_layout.tsx (نسخهی کامل)
import { ClerkProvider, useAuth } from '@clerk/expo';
import { tokenCache } from '@clerk/expo/token-cache';
import { Stack } from 'expo-router';
import { ActivityIndicator, View } from 'react-native';
function InitialLayout() {
const { isLoaded, isSignedIn } = useAuth();
if (!isLoaded) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Protected guard={isSignedIn}>
<Stack.Screen name="(app)" />
</Stack.Protected>
<Stack.Protected guard={!isSignedIn}>
<Stack.Screen name="(auth)" />
</Stack.Protected>
</Stack>
);
}
export default function RootLayout() {
return (
<ClerkProvider publishableKey={publishableKey} tokenCache={tokenCache}>
<InitialLayout />
</ClerkProvider>
);
}
منطقش ساده است:
- اگر کاربر Sign-in نکرده باشد، فقط Routeهای داخل
(auth)در دسترساند. - اگر کاربر Sign-in کرده باشد، فقط Routeهای
(app)قابل مشاهدهاند. - وقتی
guardازtrueبهfalseتغییر کند، تمام History آن Stack حذف میشود — یعنی کاربر نمیتواند با دکمهی Back دوباره به صفحات قبلی برگردد. این جزئیات کوچک ولی حیاتی است.
ساختار پیشنهادی پوشهها
app/
├── _layout.tsx # ClerkProvider + Stack.Protected
├── (auth)/
│ ├── _layout.tsx # Layout برای صفحات احراز هویت
│ ├── sign-in.tsx
│ └── sign-up.tsx
└── (app)/
├── _layout.tsx # Layout اصلی اپ (Tab یا Drawer)
├── index.tsx
└── profile.tsx
روش اول: استفاده از AuthView (سریعترین مسیر)
اگر میخواهید فقط با چند خط کد یک سیستم احراز هویت کامل و کاملاً Native داشته باشید، AuthView دقیقاً همان چیزی است که دنبالش میگردید. این کامپوننت در نسخهی ۳.۱ معرفی شد و هر روش احراز هویتی که در داشبورد Clerk فعال کرده باشید را خودکار پشتیبانی میکند:
// app/(auth)/sign-in.tsx
import { AuthView } from '@clerk/expo/native';
import { View } from 'react-native';
export default function SignInScreen() {
return (
<View style={{ flex: 1 }}>
<AuthView mode="signInOrUp" />
</View>
);
}
صادقانه بگویم، اولین باری که این کد را اجرا کردم باورم نمیشد. یک خط کد و اینهمه قابلیت: ورود با ایمیل، ثبتنام، تأیید کد ایمیل/پیامک، OAuth (Google، Apple، GitHub)، Passkey، MFA و بازیابی رمز عبور. وقتی در داشبورد یک Provider جدید فعال میکنید، AuthView آن را خودکار شناسایی میکند — بدون نیاز به Update یا Build جدید.
سفارشیسازی ظاهر AuthView
<AuthView
mode="signInOrUp"
appearance={{
colors: {
primary: '#6366F1',
background: '#FFFFFF',
},
elements: {
logoImage: { source: require('../../assets/logo.png') },
},
}}
onSignInComplete={(session) => {
console.log('کاربر وارد شد:', session.userId);
}}
/>
روش دوم: ساخت UI سفارشی با Hookها
ولی خب، همهی پروژهها یکجور نیستند. اگر برندتان UI خاص خودش را میطلبد و کنترل کامل میخواهید، Clerk Hookهای قدرتمندی برای ساخت Flow اختصاصی فراهم کرده. این روش انعطافپذیرتر است، اما طبعاً کد بیشتری میطلبد:
// app/(auth)/sign-in.tsx
import { useSignIn } from '@clerk/expo';
import { useRouter } from 'expo-router';
import React, { useState } from 'react';
import { Alert, TextInput, TouchableOpacity, Text, View } from 'react-native';
export default function SignInScreen() {
const { signIn, setActive, isLoaded } = useSignIn();
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const onSignInPress = async () => {
if (!isLoaded) return;
setLoading(true);
try {
const attempt = await signIn.create({
identifier: email,
password,
});
if (attempt.status === 'complete') {
await setActive({ session: attempt.createdSessionId });
router.replace('/');
} else {
// در صورت نیاز به MFA به این بخش میرسد
console.log('وضعیت بعدی:', attempt.status);
}
} catch (err: any) {
Alert.alert('خطا', err.errors?.[0]?.message ?? 'ورود ناموفق');
} finally {
setLoading(false);
}
};
return (
<View style={{ padding: 24, flex: 1, justifyContent: 'center' }}>
<TextInput
placeholder="ایمیل"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
style={{ borderWidth: 1, padding: 12, marginBottom: 12 }}
/>
<TextInput
placeholder="رمز عبور"
value={password}
onChangeText={setPassword}
secureTextEntry
style={{ borderWidth: 1, padding: 12, marginBottom: 12 }}
/>
<TouchableOpacity
onPress={onSignInPress}
disabled={loading}
style={{ backgroundColor: '#6366F1', padding: 14, borderRadius: 8 }}
>
<Text style={{ color: 'white', textAlign: 'center' }}>
{loading ? 'در حال ورود...' : 'ورود'}
</Text>
</TouchableOpacity>
</View>
);
}
پیادهسازی Sign-Up با تأیید ایمیل
// app/(auth)/sign-up.tsx
import { useSignUp } from '@clerk/expo';
import { useRouter } from 'expo-router';
import { useState } from 'react';
export default function SignUpScreen() {
const { isLoaded, signUp, setActive } = useSignUp();
const [pendingVerification, setPendingVerification] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [code, setCode] = useState('');
const router = useRouter();
const onSignUpPress = async () => {
if (!isLoaded) return;
try {
await signUp.create({ emailAddress: email, password });
await signUp.prepareEmailAddressVerification({ strategy: 'email_code' });
setPendingVerification(true);
} catch (err: any) {
console.error(err.errors[0].message);
}
};
const onVerifyPress = async () => {
if (!isLoaded) return;
try {
const attempt = await signUp.attemptEmailAddressVerification({ code });
if (attempt.status === 'complete') {
await setActive({ session: attempt.createdSessionId });
router.replace('/');
}
} catch (err: any) {
console.error(err.errors[0].message);
}
};
// ... (UI کد را در دو حالت متفاوت رندر کنید)
}
ورود با Google و Apple به صورت کاملاً Native
یکی از بزرگترین دردسرهای قبل از نسخهی ۳.۱، باز شدن مرورگر برای OAuth بود. تجربهی کاربری ضعیفی داشت و تأخیرش هم اعصابخردکن بود. حالا Clerk از Native Google Sign-In استفاده میکند که در iOS از ASAuthorization و در Android از Credential Manager بهره میگیرد:
import { useSSO } from '@clerk/expo';
import * as WebBrowser from 'expo-web-browser';
import { useEffect } from 'react';
WebBrowser.maybeCompleteAuthSession();
export function GoogleSignInButton() {
const { startSSOFlow } = useSSO();
const onPress = async () => {
try {
const { createdSessionId, setActive } = await startSSOFlow({
strategy: 'oauth_google',
});
if (createdSessionId && setActive) {
await setActive({ session: createdSessionId });
}
} catch (err) {
console.error('OAuth error', err);
}
};
return <Button title="ورود با Google" onPress={onPress} />;
}
برای Apple Sign-In هم به همین شکل، فقط کافی است از strategy: 'oauth_apple' استفاده کنید. در داشبورد Clerk هر دو Provider باید فعال شوند و در iOS باید قابلیت Sign In with Apple در App Identifier فعال باشد (اگر این مرحله را فراموش کنید، Apple با یک پیام خطای مبهم به استقبالتان میآید — تجربهی شخصی).
پیادهسازی Passkey
Passkey در ۲۰۲۶ به استاندارد طلایی احراز هویت تبدیل شده: بدون رمز عبور، مقاوم در برابر فیشینگ و سازگار با Face ID/Touch ID. Clerk با پکیج @clerk/expo-passkeys پیادهسازیاش را به یک خط کد تقلیل داده است:
import { useUser } from '@clerk/expo';
export function CreatePasskeyButton() {
const { user } = useUser();
const handleCreatePasskey = async () => {
if (!user) return;
try {
await user.createPasskey();
Alert.alert('موفق', 'Passkey با موفقیت ساخته شد');
} catch (err: any) {
Alert.alert('خطا', err.message);
}
};
return <Button title="ساخت Passkey" onPress={handleCreatePasskey} />;
}
برای ورود با Passkey:
import { useSignIn } from '@clerk/expo';
const { signIn } = useSignIn();
const result = await signIn.authenticateWithPasskey();
if (result.status === 'complete') {
await setActive({ session: result.createdSessionId });
}
یک یادآوری مهم: Passkey به Associated Domains در iOS و Digital Asset Links در Android نیاز دارد. این فایلها باید روی دامنهی شما (مثلاً https://yourdomain.com/.well-known/apple-app-site-association) منتشر شده باشند. اگر این مرحله را رد کنید، Passkey ساکت و بیسروصدا fail میشود — و دیباگش هم چندان لذتبخش نیست.
احراز هویت دو/چند عاملی (MFA)
وقتی کاربر MFA را روی حساب خود فعال کرده باشد، پس از ورود اولیه، وضعیت signIn به جای complete برابر needs_second_factor میشود. در این حالت باید فاکتور دوم را درخواست کنید:
const attempt = await signIn.create({ identifier: email, password });
if (attempt.status === 'needs_second_factor') {
// ارسال کد به TOTP، SMS یا Backup Code
const supportedFactors = attempt.supportedSecondFactors;
const totpFactor = supportedFactors?.find(
(f) => f.strategy === 'totp'
);
if (totpFactor) {
const verification = await signIn.attemptSecondFactor({
strategy: 'totp',
code: userEnteredCode, // از TextInput بگیرید
});
if (verification.status === 'complete') {
await setActive({ session: verification.createdSessionId });
}
}
}
روشهای پشتیبانیشده در ۲۰۲۶: TOTP (Authenticator App)، SMS، Backup Codes و Phone Code. MFA در پلن Pro کلرک (۲۰ دلار در ماه) در دسترس است.
دسترسی به اطلاعات کاربر
Hook اصلی برای دریافت اطلاعات کاربر useUser() است. ساده و سرراست:
import { useUser } from '@clerk/expo';
export default function ProfileScreen() {
const { isLoaded, isSignedIn, user } = useUser();
if (!isLoaded) return <LoadingSpinner />;
if (!isSignedIn) return <Redirect href="/sign-in" />;
return (
<View>
<Text>سلام {user.firstName}</Text>
<Text>ایمیل: {user.primaryEmailAddress?.emailAddress}</Text>
<Image
source={{ uri: user.imageUrl }}
style={{ width: 80, height: 80, borderRadius: 40 }}
/>
</View>
);
}
برای ویرایش پروفایل، به جای ساخت UI سفارشی میتوانید از UserProfileView یا Hook جدید useUserProfileModal() استفاده کنید. به نظر من، مگر اینکه نیاز خاصی داشته باشید، صفحهی پروفایل پیشفرض Clerk کار را راه میاندازد.
فراخوانی API محافظتشده با JWT
برای دریافت توکن JWT جهت ارسال به Backend خودتان:
import { useAuth } from '@clerk/expo';
function useAuthenticatedFetch() {
const { getToken } = useAuth();
return async (url: string, options: RequestInit = {}) => {
const token = await getToken();
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
};
}
توکنها به صورت خودکار قبل از منقضی شدن Refresh میشوند. در سمت Backend هم از کتابخانهی @clerk/backend برای اعتبارسنجی JWT استفاده کنید (لطفاً هرگز این مرحله را skip نکنید — جلوتر در بخش امنیت بیشتر توضیح میدهم).
ایجاد Development Build
گفتیم که AuthView و کامپوننتهای Native در Expo Go کار نمیکنند. پس باید یک Development Build بسازیم:
# Build محلی برای iOS
npx expo run:ios
# Build محلی برای Android
npx expo run:android
# یا با EAS برای Build ابری
eas build --profile development --platform ios
eas build --profile development --platform android
در فایل eas.json پروفایل Development باید developmentClient را فعال کرده باشد:
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
}
}
}
Sign-Out و پاکسازی Session
import { useAuth } from '@clerk/expo';
function SignOutButton() {
const { signOut } = useAuth();
const handleSignOut = async () => {
await signOut();
// Stack.Protected کاربر را خودکار به صفحهی Sign-In هدایت میکند
};
return <Button title="خروج" onPress={handleSignOut} />;
}
رفع خطاهای رایج
خطای Missing Publishable Key
اگر این خطا را میبینید، اول مطمئن شوید فایل .env.local در Root پروژه است و نام متغیر دقیقاً با پیشوند EXPO_PUBLIC_ شروع میشود. بعد از تغییر فایل .env، سرور را با npx expo start --clear Restart کنید — این --clear خیلی وقتها فرق روز و شب را میسازد.
AuthView is not a function
این خطا یعنی هنوز در حال اجرای پروژه با Expo Go هستید. به Development Build سوییچ کنید: npx expo run:ios.
Token Cache not working
اگر کاربر بعد از Restart اپ Sign-out میشود، چک کنید که tokenCache به درستی به ClerkProvider پاس داده شده باشد و expo-secure-store هم نصب باشد. در Simulator گاهی Keychain ریست میشود — این رفتار طبیعی است و روی دستگاه واقعی اتفاق نمیافتد.
OAuth Redirect Failure
در داشبورد Clerk بخش SSO Connections باید Bundle ID اپلیکیشن iOS و Package Name اندروید به درستی ثبت شده باشد. همچنین scheme در app.json باید با مقدار ثبتشده در Clerk یکی باشد. همینجا چند ساعت از عمر من رفت تا فهمیدم scheme در دو جا متفاوت تنظیم شده بود.
بهترین روشهای امنیتی در ۲۰۲۶
- هرگز توکن را در AsyncStorage ذخیره نکنید: فقط از
expo-secure-storeیاtokenCacheClerk استفاده کنید. AsyncStorage رمزنگاری ندارد و توکنها به سادگی قابل خواندناند. - Bundle ID اپلیکیشن خود را در Clerk Dashboard لاک کنید تا Publishable Key شما در اپ دیگری استفاده نشود.
- JWT را همیشه در Backend اعتبارسنجی کنید — هرگز فقط به Headerهای HTTP اعتماد نکنید. این یک قانون طلایی است.
- Passkey را به عنوان روش اصلی پیشنهاد دهید و رمز عبور را به عنوان Fallback نگه دارید.
- قبل از درخواست Permissionهای حساس یک Pre-Permission Screen نمایش دهید تا کاربر بفهمد چرا نیاز به دسترسی دارید.
- از Privacy Manifest در iOS برای اعلام جمعآوری دادههای احراز هویت استفاده کنید (الزام App Store از ۲۰۲۴ به بعد).
سؤالات متداول
آیا Clerk با React Native CLI (بدون Expo) کار میکند؟
بله، اما برای پروژههای CLI خالص باید از پکیج @clerk/clerk-react-native استفاده کنید که قابلیتهای Native کمتری دارد. @clerk/expo فقط با Expo SDK 53+ سازگار است و کامپوننتهای Native (مانند AuthView) فقط در نسخهی Expo در دسترس هستند. صادقانه، برای پروژههای جدید در ۲۰۲۶، استفاده از Expo توصیه میشود.
هزینهی Clerk چقدر است و آیا پلن رایگان دارد؟
Clerk دارای پلن رایگانی است که تا ۱۰,۰۰۰ MAU (کاربر فعال ماهانه) را پوشش میدهد و شامل احراز هویت پایه، OAuth و مدیریت کاربر است. MFA و قابلیتهای پیشرفته (مثل Organizations و SSO سازمانی) در پلن Pro با هزینهی ۲۰ دلار در ماه (سالانه) در دسترساند. برای استارتاپها این یکی از سخاوتمندانهترین پلنهای رایگان در میان Providerهای احراز هویت است.
تفاوت AuthView با Hookهای سفارشی چیست؟
AuthView یک کامپوننت Drop-in است که UI کاملاً Native (SwiftUI/Jetpack Compose) را با حداقل کد رندر میکند و تمام Flowها (ورود، ثبتنام، تأیید، MFA، OAuth) را خودکار مدیریت میکند. Hookهای سفارشی (مانند useSignIn) به شما کنترل کامل بر UI میدهند اما کدنویسی بیشتری میطلبند. توصیهی ما این است: اگر برندینگ یا UI کاملاً سفارشی نمیخواهید، با AuthView شروع کنید — میتوانید بعداً به Hookها مهاجرت کنید.
آیا میتوانم در Expo Go تست کنم؟
برای Hookها (مانند useSignIn، useAuth) و Flowهای سادهی ایمیل/رمز عبور بله، Expo Go کار میکند. اما برای AuthView، Native Google Sign-In و Passkey باید Development Build بسازید. توصیهی ۲۰۲۶ این است که از همان ابتدا Development Build بسازید — جریان توسعه روانتر است و راستش، Expo Go در آیندهی نزدیک قابلیتهای بیشتری را از دست خواهد داد.
چگونه به جای ایمیل از شماره موبایل برای احراز هویت استفاده کنم؟
در داشبورد Clerk بخش User & Authentication > Email, Phone, Username، گزینهی Phone را فعال و Email را غیرفعال کنید. سپس در کد به جای emailAddress از phoneNumber در signUp.create استفاده کنید و به جای prepareEmailAddressVerification از preparePhoneNumberVerification. Clerk به صورت خودکار پیامک تأیید را با Twilio یا Provider انتخابی شما ارسال میکند.
جمعبندی
راستش را بخواهید، در سال ۲۰۲۶ پیادهسازی احراز هویت در React Native دیگر یک کابوس چندهفتهای نیست. ترکیب Clerk Expo SDK 3.1 با Stack.Protected در Expo Router v5 به شما اجازه میدهد فقط با چند ده خط کد یک سیستم کامل با Passkey، MFA، OAuth Native و UI واقعاً Native داشته باشید.
اگر سرعت برایتان مهم است، با AuthView شروع کنید. اگر کنترل بیشتری میخواهید، Hookها در دسترساند. در هر دو مسیر، توکنها به صورت امن در Keychain/Keystore ذخیره میشوند، Sessionها بین Restartها پایدار میمانند و حالت احراز هویت در سراسر اپ همگام است. حالا وقت آن است که این الگوها را در پروژهی بعدی خود به کار ببرید — و اگر در راه به مشکلی خوردید، یادتان باشد که Stack Overflow بهترین دوست شماست.