adding xApiKey Valaidaion Issue Resolve Documentation ...

This commit is contained in:
2026-10-10 22:15:12 +03:30
parent f23c87e923
commit f3aaaa1256
@@ -0,0 +1,920 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>رفع مشکل تست XApiKey در Swagger | مستند فنی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
--code-bg: #f7fafc;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 1000px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title h1 {
font-size: 26px;
font-weight: 800;
margin-bottom: 8px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body { padding: 40px; }
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 45px; height: 45px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 18px;
flex-shrink: 0;
}
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.code-block {
background: var(--code-bg);
border: 1px solid var(--border);
border-right: 4px solid var(--accent);
border-radius: 6px;
padding: 15px 20px;
margin: 15px 0;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
overflow-x: auto;
direction: ltr;
text-align: left;
line-height: 1.6;
white-space: pre-wrap;
}
.code-block .comment { color: #6a737d; }
.code-block .keyword { color: #d73a49; }
.code-block .string { color: #032f62; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong { color: var(--accent-dark); }
.warning-box {
background: linear-gradient(to left, #fff5f5, #fffaf0);
border: 1px solid #feb2b2;
border-right: 4px solid var(--danger);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box strong { color: var(--danger); }
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box strong { color: var(--success); }
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.flow-diagram {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 25px;
margin: 20px 0;
text-align: center;
}
.flow-step {
display: inline-block;
background: white;
border: 2px solid var(--primary-light);
border-radius: 8px;
padding: 10px 18px;
margin: 5px;
font-size: 13px;
font-weight: 600;
color: var(--primary);
}
.flow-arrow {
display: inline-block;
color: var(--accent);
font-size: 20px;
margin: 0 8px;
font-weight: bold;
}
.flow-step.error {
border-color: var(--danger);
color: var(--danger);
background: #fff5f5;
}
.flow-step.success {
border-color: var(--success);
color: var(--success);
background: #f0fff4;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
th, td {
padding: 12px 16px;
border: 1px solid var(--border);
text-align: right;
font-size: 13px;
}
th {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
font-weight: 600;
}
tr:nth-child(even) { background: #f8fafc; }
.step-card {
background: white;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
margin: 20px 0;
position: relative;
}
.step-card.critical {
border-color: var(--danger);
background: #fff5f5;
}
.step-card.important {
border-color: var(--accent);
background: #fffaf0;
}
.step-badge {
position: absolute;
top: -12px;
right: 20px;
background: var(--primary);
color: white;
padding: 3px 14px;
border-radius: 15px;
font-size: 12px;
font-weight: 700;
}
.step-badge.critical { background: var(--danger); }
.step-badge.important { background: var(--accent); }
.step-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 12px;
margin-top: 8px;
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
}
.footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
ul.checklist {
list-style: none;
padding: 0;
margin: 15px 0;
}
ul.checklist li {
padding: 8px 0 8px 30px;
position: relative;
border-bottom: 1px solid var(--border);
}
ul.checklist li:before {
content: "✓";
position: absolute;
right: 0;
color: var(--success);
font-weight: bold;
font-size: 16px;
}
ul.checklist li.fail:before {
content: "✗";
color: var(--danger);
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="doc-meta">
<div>شماره مستند: XIDS-SWAGGER-APIKEY-002</div>
<div>نسخه: ۲.۰</div>
<div>تاریخ تهیه: ۲۰ مهر ۱۴۰۵</div>
<div>طبقه‌بندی: داخلی — فنی</div>
</div>
</div>
<div class="document-title">
<h1>رفع مشکل تست XApiKey در Swagger UI</h1>
<h2>تحلیل ریشه‌ای و راه‌حل گام‌به‌گام برای فعال‌سازی کامل</h2>
</div>
</header>
<div class="document-body">
<!-- بخش ۱: خلاصه مشکل -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>خلاصه مشکل شناسایی‌شده</h3>
<div class="subtitle">Problem Summary</div>
</div>
</div>
<div class="section-content">
<p>
پس از بررسی دقیق کدهای پروژه <code>xIds</code>، مشخص شد که اگرچه زیرساخت
<code>XApiKeyAuthenticationHandler</code> و Policyهای مربوطه به‌درستی پیاده‌سازی شده‌اند،
اما <strong>چهار مشکل معماری</strong> مانع از کارکرد صحیح احراز هویت با API Key در Swagger می‌شوند.
</p>
<div class="warning-box">
<strong>🔴 مشکلات شناسایی‌شده:</strong>
<ol style="padding-right: 20px; margin-top: 10px;">
<li><strong>ترتیب ثبت Schemeها:</strong> <code>XApiKey</code> اول ثبت شده و به‌عنوان DefaultScheme انتخاب می‌شود — در نتیجه درخواست‌های Bearer Token هم به آن حواله می‌شوند و شکست می‌خورند.</li>
<li><strong>عدم تنظیم DefaultScheme:</strong> در <code>AddAuthentication()</code> هیچ DefaultScheme مشخصی تعیین نشده است.</li>
<li><strong>محدودیت LocalApi Policy:</strong> Policy <code>IdentityServerConstants.LocalApi.PolicyName</code> فقط Scheme <code>IdentityServerAccessToken</code> را قبول می‌کند.</li>
<li><strong>عدم وجود PolicyScheme هوشمند:</strong> هیچ مکانیزمی برای انتخاب پویای Scheme بر اساس Header درخواست وجود ندارد.</li>
</ol>
</div>
</div>
</div>
<!-- بخش ۲: جریان فعلی -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>تحلیل جریان فعلی احراز هویت</h3>
<div class="subtitle">Current Authentication Flow Analysis</div>
</div>
</div>
<div class="section-content">
<p>
در کد فعلی <code>XDIHelperExtension.AddXIdentityServerAuthentication</code>، ترتیب ثبت به این صورت است:
</p>
<div class="code-block"><span class="comment">// xIds/DI/XDIHelperExtension.cs — کد فعلی</span>
<span class="keyword">var</span> authBuilder = services.AddAuthentication(); <span class="comment">// ← DefaultScheme تنظیم نشده!</span>
<span class="comment">// ۱. ثبت XApiKey (اول)</span>
<span class="keyword">if</span> (addApiKeyAuthentication)
{
authBuilder.AddScheme&lt;..., XApiKeyAuthenticationHandler&gt;(
<span class="string">"XApiKey"</span>, options =&gt; { });
}
<span class="comment">// ۲. ثبت JwtBearer</span>
authBuilder.AddJwtBearer(options =&gt; {
options.ForwardDefaultSelector = context =&gt; { ... };
})
<span class="comment">// ۳. ثبت LocalApi</span>
.AddLocalApi();</div>
<div class="highlight-box">
<strong>💡 نکته کلیدی:</strong>
وقتی <code>AddAuthentication()</code> بدون پارامتر فراخوانی می‌شود و سپس اولین Scheme (<code>XApiKey</code>) ثبت می‌گردد،
ASP.NET Core به‌صورت خودکار <code>XApiKey</code> را به‌عنوان <strong>DefaultScheme</strong> در نظر می‌گیرد.
این یعنی تمام درخواست‌ها (حتی آن‌هایی که Bearer Token دارند) ابتدا به <code>XApiKeyAuthenticationHandler</code> می‌روند
و چون Header <code>X-Api-Key</code> وجود ندارد، <code>NoResult</code> برمی‌گردانند.
</div>
<div class="flow-diagram">
<div style="margin-bottom: 15px; font-weight: 700; color: var(--primary);">جریان فعلی (معیوب):</div>
<span class="flow-step">درخواست با Bearer Token</span>
<span class="flow-arrow">→</span>
<span class="flow-step error">DefaultScheme = XApiKey</span>
<span class="flow-arrow">→</span>
<span class="flow-step error">NoResult (بدون Header)</span>
<span class="flow-arrow">→</span>
<span class="flow-step error">401 Unauthorized ❌</span>
</div>
<div class="flow-diagram">
<div style="margin-bottom: 15px; font-weight: 700; color: var(--primary);">جریان مطلوب (پس از اصلاح):</div>
<span class="flow-step">درخواست</span>
<span class="flow-arrow">→</span>
<span class="flow-step success">PolicyScheme (هوشمند)</span>
<span class="flow-arrow">→</span>
<span class="flow-step">XApiKey یا LocalApi</span>
<span class="flow-arrow">→</span>
<span class="flow-step success">200 OK ✓</span>
</div>
</div>
</div>
<!-- بخش ۳: راه‌حل گام ۱ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>گام ۱ — اصلاح AddXIdentityServerAuthentication با PolicyScheme</h3>
<div class="subtitle">Step 1: Fix Authentication Registration with PolicyScheme</div>
</div>
</div>
<div class="section-content">
<div class="step-card critical">
<div class="step-badge critical">حیاتی — فایل: xIds/DI/XDIHelperExtension.cs</div>
<h4>توضیح:</h4>
<p>
باید از <code>AddPolicyScheme</code> استفاده کنیم تا یک Scheme هوشمند بسازیم که بر اساس
وجود Header <code>X-Api-Key</code> در درخواست، Scheme مناسب را انتخاب کند.
همچنین DefaultScheme و DefaultChallengeScheme باید به‌درستی تنظیم شوند.
</p>
</div>
<p><strong>کد اصلاح‌شده کامل:</strong></p>
<div class="code-block"><span class="comment">// xIds/DI/XDIHelperExtension.cs</span>
<span class="comment">// متد: AddXIdentityServerAuthentication</span>
<span class="keyword">public static void</span> AddXIdentityServerAuthentication(
<span class="keyword">this</span> IServiceCollection services,
IConfiguration configuration,
<span class="keyword">bool</span> addApiKeyAuthentication = <span class="keyword">false</span>
)
{
<span class="comment">// افزودنی‌های مورد نیاز</span>
services.AddXIdentityResourceConfiguration(configuration);
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="comment">// ثبت XApiKey Scheme (قبل از هر چیز دیگر)</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="keyword">if</span> (addApiKeyAuthentication)
{
services.AddAuthentication()
.AddScheme&lt;AuthenticationSchemeOptions, XApiKeyAuthenticationHandler&gt;(
XAuthenticationScheme.XApiKey.GetStringValue(),
options =&gt; { });
}
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="comment">// تعریف PolicyScheme هوشمند برای مسیریابی درخواست‌ها</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="keyword">var</span> smartSchemeName = <span class="string">"SmartAuthScheme"</span>;
services.AddAuthentication(options =&gt;
{
<span class="comment">// DefaultScheme = PolicyScheme هوشمند</span>
options.DefaultScheme = smartSchemeName;
options.DefaultAuthenticateScheme = smartSchemeName;
options.DefaultChallengeScheme = smartSchemeName;
})
.AddPolicyScheme(smartSchemeName, <span class="string">"Smart Authentication Scheme"</span>, options =&gt;
{
options.ForwardDefaultSelector = context =&gt;
{
<span class="comment">// بررسی وجود Header X-Api-Key</span>
<span class="keyword">var</span> apiKeyHeader = XHeader.ApiKey.GetStringValue();
<span class="keyword">if</span> (context.Request.Headers.ContainsKey(apiKeyHeader))
{
<span class="keyword">return</span> XAuthenticationScheme.XApiKey.GetStringValue();
}
<span class="comment">// در غیر این صورت، از LocalApi (IdentityServer) استفاده کن</span>
<span class="keyword">return</span> XAuthentication.IDENTITY_SERVER_LOCAL_API;
};
})
.AddJwtBearer(XAuthenticationScheme.XToken.GetStringValue(), options =&gt;
{
options.Authority = configuration
.GetSection(<span class="string">"IdentityResourceConfiguration:Authority"</span>).Value;
options.TokenValidationParameters.ValidateAudience = <span class="keyword">false</span>;
options.TokenValidationParameters.ValidTypes = <span class="keyword">new</span>[] { <span class="string">"at+jwt"</span> };
})
.AddOAuth2Introspection(XAuthenticationScheme.XIntrospection.GetStringValue(), options =&gt;
{
<span class="keyword">var</span> identityConfig = configuration.GetXIdentityResourceConfiguration();
options.Authority = identityConfig.Authority;
options.ClientId = identityConfig.ApiName;
options.ClientSecret = identityConfig.ClientSecret;
})
.AddLocalApi();
}</div>
<div class="success-box">
<strong>✅ چرا این راه‌حل کار می‌کند؟</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li><code>PolicyScheme</code> به‌عنوان DefaultScheme عمل می‌کند و بر اساس Header تصمیم می‌گیرد.</li>
<li>اگر <code>X-Api-Key</code> وجود داشته باشد → به <code>XApiKeyAuthenticationHandler</code> حواله می‌دهد.</li>
<li>در غیر این صورت → به <code>LocalApi</code> (IdentityServer) حواله می‌دهد.</li>
<li>هیچ‌گاه Scheme اشتباه انتخاب نمی‌شود.</li>
</ul>
</div>
</div>
</div>
<!-- بخش ۴: راه‌حل گام ۲ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>گام ۲ — اصلاح LocalApi Policy برای پذیرش XApiKey</h3>
<div class="subtitle">Step 2: Fix LocalApi Policy to Accept XApiKey</div>
</div>
</div>
<div class="section-content">
<div class="step-card important">
<div class="step-badge important">مهم — فایل: xIds/DI/XDIHelperExtension.cs</div>
<h4>توضیح:</h4>
<p>
در متد <code>AddXAuthorization</code>، Policy <code>LocalApi.PolicyName</code>
فقط Scheme <code>IdentityServerAccessToken</code> را قبول می‌کند.
باید این Policy را اصلاح کنیم تا <code>XApiKey</code> را هم بپذیرد.
</p>
</div>
<div class="code-block"><span class="comment">// xIds/DI/XDIHelperExtension.cs</span>
<span class="comment">// متد: AddXAuthorization</span>
<span class="keyword">public static void</span> AddXAuthorization(
<span class="keyword">this</span> IServiceCollection services,
IDictionary&lt;<span class="keyword">string</span>, AuthorizationPolicy&gt; policies = <span class="keyword">null</span>
)
{
services.AddAuthorization(options =&gt;
{
<span class="comment">// ... کدهای قبلی برای policies و xPolicies ...</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
<span class="comment">// اصلاح LocalApi Policy برای پذیرش هر دو Scheme</span>
<span class="comment">// ═══════════════════════════════════════════════════════</span>
options.AddPolicy(IdentityServerConstants.LocalApi.PolicyName, policy =&gt;
{
policy.AddAuthenticationSchemes(<span class="keyword">new</span>[]
{
IdentityServerConstants.LocalApi.AuthenticationScheme,
XAuthenticationScheme.XApiKey.GetStringValue() <span class="comment">// ← اضافه شد</span>
});
policy.RequireAuthenticatedUser();
});
});
services.AddSingleton&lt;IAuthorizationHandler, RequiredRolesHandler&gt;();
}</div>
<div class="info-box">
<strong>💡 نکته:</strong>
با این اصلاح، Endpointهایی که <code>[Authorize(LocalApi.PolicyName)]</code> دارند،
هم با Bearer Token و هم با X-Api-Key قابل دسترسی خواهند بود.
</div>
</div>
</div>
<!-- بخش ۵: راه‌حل گام ۳ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>گام ۳ — بررسی و اصلاح Swagger Security Definition</h3>
<div class="subtitle">Step 3: Verify Swagger Security Configuration</div>
</div>
</div>
<div class="section-content">
<div class="step-card">
<div class="step-badge">بررسی — فایل: xCommons/Extensions/DIExtensions.cs</div>
<h4>توضیح:</h4>
<p>
در کد فعلی <code>AddXSwaggerGenOptions</code>، Security Definition برای XApiKey
به‌درستی تعریف شده است. اما باید مطمئن شویم که <code>addXApiKeyAuthorization</code>
در فراخوانی <code>AddXSwagger</code> مقدار <code>true</code> دارد.
</p>
</div>
<p><strong>بررسی Startup.cs:</strong></p>
<div class="code-block"><span class="comment">// xIds/Startup.cs — ConfigureServices</span>
<span class="comment">// ثبت Swagger — مطمئن شوید پارامترها درست هستند</span>
<span class="keyword">var</span> xmlFile = $<span class="string">"{Assembly.GetExecutingAssembly().GetName().Name}.xml"</span>;
<span class="keyword">var</span> xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
services.AddXSwagger(
Configuration,
xmlFilePath: xmlPath,
addRequiredXPoweredFilter: <span class="keyword">true</span>,
addXTokenAuthorization: <span class="keyword">true</span>,
addXApiKeyAuthorization: <span class="keyword">true</span>, <span class="comment">// ← باید true باشد</span>
addDefaultValueFilter: <span class="keyword">true</span>
);</div>
<p><strong>خروجی مورد انتظار در Swagger UI:</strong></p>
<div class="highlight-box">
<strong>🔑 در Swagger UI باید دو دکمه "Authorize" ببینید:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li><strong>accessToken:</strong> برای Bearer Token (مقدار: <code>Bearer {token}</code>)</li>
<li><strong>X-Api-Key:</strong> برای API Key (مقدار: <code>xapp_xxxxxxxxxxxx</code>)</li>
</ul>
</div>
</div>
</div>
<!-- بخش ۶: راه‌حل گام ۴ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>گام ۴ — اصلاح XApiKeyAuthenticationHandler</h3>
<div class="subtitle">Step 4: Verify XApiKeyAuthenticationHandler</div>
</div>
</div>
<div class="section-content">
<div class="step-card">
<div class="step-badge">بررسی — فایل: xIds/Providers/XApiKeyAuthenticationHandler.cs</div>
<h4>توضیح:</h4>
<p>
Handler فعلی به‌درستی پیاده‌سازی شده است. فقط یک نکته مهم:
مطمئن شوید که <code>ValidateApiKey</code> در <code>XApplicationProvider</code>
مقدار <code>requiredScope</code> را به‌درستی بررسی می‌کند.
</p>
</div>
<p><strong>کد فعلی Handler (صحیح است):</strong></p>
<div class="code-block"><span class="comment">// xIds/Providers/XApiKeyAuthenticationHandler.cs</span>
<span class="keyword">protected override async</span> Task&lt;AuthenticateResult&gt; HandleAuthenticateAsync()
{
<span class="comment">// ۱. استخراج API Key از Header</span>
<span class="keyword">var</span> apiKeyHeader = HeaderName; <span class="comment">// "X-Api-Key"</span>
<span class="keyword">if</span> (!Request.Headers.ContainsKey(apiKeyHeader))
<span class="keyword">return</span> AuthenticateResult.NoResult();
<span class="keyword">var</span> apiKey = Request.Headers[apiKeyHeader].ToString();
<span class="keyword">if</span> (apiKey.IsNullOrEmpty())
<span class="keyword">return</span> AuthenticateResult.NoResult();
<span class="comment">// ۲. استخراج Client IP</span>
<span class="keyword">var</span> clientIP = Request.HttpContext.Connection.RemoteIpAddress?.ToString();
<span class="comment">// ۳. اعتبارسنجی API Key</span>
<span class="keyword">var</span> validationResult = <span class="keyword">await</span> applicationProvider.ValidateApiKey(
apiKey: apiKey,
clientIP: clientIP
);
<span class="keyword">if</span> (validationResult.Errors.HasChild())
{
<span class="keyword">var</span> message = validationResult.Errors.ToListString(<span class="string">'\n'</span>);
<span class="keyword">return</span> AuthenticateResult.Fail(message);
}
<span class="comment">// ۴. ساخت Claims</span>
<span class="keyword">var</span> claims = <span class="keyword">new</span> List&lt;Claim&gt; {
<span class="keyword">new</span>(ClaimTypes.Name, validationResult.OwnerId),
<span class="keyword">new</span>(JwtClaimTypes.Subject, validationResult.OwnerId),
<span class="keyword">new</span>(XCustomClaims.ApplicationId, validationResult.ApplicationId.ToString()),
<span class="keyword">new</span>(XCustomClaims.AuthType, <span class="string">"apikey"</span>),
};
<span class="comment">// ۵. اضافه کردن Scopeها</span>
<span class="keyword">if</span> (validationResult.Scopes != <span class="keyword">null</span>)
{
<span class="keyword">foreach</span> (<span class="keyword">var</span> scope <span class="keyword">in</span> validationResult.Scopes)
{
claims.Add(<span class="keyword">new</span> Claim(JwtClaimTypes.Scope, scope));
}
}
<span class="comment">// ۶. ساخت Principal و Ticket</span>
<span class="keyword">var</span> identity = <span class="keyword">new</span> ClaimsIdentity(claims, Scheme.Name);
<span class="keyword">var</span> principal = <span class="keyword">new</span> ClaimsPrincipal(identity);
<span class="keyword">var</span> ticket = <span class="keyword">new</span> AuthenticationTicket(principal, Scheme.Name);
<span class="keyword">return</span> AuthenticateResult.Success(ticket);
}</div>
<div class="success-box">
<strong>✅ وضعیت Handler:</strong>
کد فعلی Handler صحیح است و نیازی به تغییر ندارد.
فقط مطمئن شوید که <code>validationResult.Scopes</code> مقادیر صحیح
(<code>read</code>, <code>write</code>, <code>admin</code>, <code>manage</code>)
را برمی‌گرداند.
</div>
</div>
</div>
<!-- بخش ۷: تست در Swagger -->
<div class="section">
<div class="section-header">
<div class="section-number">۷</div>
<div class="section-title">
<h3>گام ۵ — راهنمای تست در Swagger UI</h3>
<div class="subtitle">Step 5: Testing Guide in Swagger UI</div>
</div>
</div>
<div class="section-content">
<p><strong>مراحل تست گام‌به‌گام:</strong></p>
<div class="step-card">
<div class="step-badge">تست ۱</div>
<h4>ساخت Application و API Key</h4>
<p>ابتدا یک Application بسازید و سپس یک API Key برای آن ایجاد کنید:</p>
<div class="code-block"><span class="comment"># ۱. ساخت Application (با Bearer Token احراز هویت کنید)</span>
POST /Applications
Content-Type: application/json
Authorization: Bearer {access_token}
X-PoweredBy: {xPoweredValue}
{
"name": "TestApp",
"description": "Test Application",
"type": 0
}
<span class="comment"># ۲. ساخت API Key (با Bearer Token)</span>
POST /Applications/{applicationId}/ApiKeys/Create
Content-Type: application/json
Authorization: Bearer {access_token}
X-PoweredBy: {xPoweredValue}
{
"scopes": ["read", "write", "manage"],
"rateLimit": 60,
"expirationMinutes": 43200,
"allowedIPs": ["127.0.0.1", "::1"]
}
<span class="comment"># پاسخ شامل plainKey است — آن را ذخیره کنید!</span>
<span class="comment"># مثال: xapp_abc123def456...</span></div>
</div>
<div class="step-card">
<div class="step-badge">تست ۲</div>
<h4>استفاده از API Key در Swagger UI</h4>
<p>در Swagger UI:</p>
<ol style="padding-right: 20px; margin-top: 10px;">
<li>روی دکمه <strong>"Authorize"</strong> (بالا سمت راست) کلیک کنید.</li>
<li>در بخش <strong>"X-Api-Key"</strong>، کلید API دریافتی را وارد کنید.</li>
<li>روی <strong>"Authorize"</strong> و سپس <strong>"Close"</strong> کلیک کنید.</li>
<li>Endpoint <code>GET /Account/Test/HiApiKeyAccess</code> را پیدا کنید.</li>
<li>روی <strong>"Try it out"</strong> و سپس <strong>"Execute"</strong> کلیک کنید.</li>
</ol>
</div>
<div class="step-card">
<div class="step-badge">تست ۳</div>
<h4>نتایج مورد انتظار</h4>
<table>
<thead>
<tr>
<th>سناریو</th>
<th>پاسخ مورد انتظار</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
<tr>
<td>API Key معتبر + Scope کافی</td>
<td><code>200 OK</code> — "Hi ApiKey Access, is Passed ..."</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>API Key نامعتبر</td>
<td><code>401 Unauthorized</code> — "ApiKey not found"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>API Key منقضی‌شده</td>
<td><code>401</code> — "ApiKey is Expired"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>Scope ناکافی</td>
<td><code>403 Forbidden</code></td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>IP غیرمجاز</td>
<td><code>401</code> — "Client IP not Allowed"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
<tr>
<td>Rate Limit رد شده</td>
<td><code>401</code> — "ApiKey Rate Limit Reached"</td>
<td style="color: var(--success); font-weight: 600;">✓ صحیح</td>
</tr>
</tbody>
</table>
</div>
<div class="step-card">
<div class="step-badge">تست ۴</div>
<h4>تست با cURL (برای اطمینان بیشتر)</h4>
<div class="code-block"><span class="comment"># تست مستقیم با cURL</span>
curl -X GET "https://localhost:5001/Account/Test/HiApiKeyAccess" \
-H "X-Api-Key: xapp_your_api_key_here" \
-H "X-PoweredBy: your_powered_value" \
-k
<span class="comment"># پاسخ مورد انتظار:</span>
<span class="comment"># "Hi ApiKey Access, is Passed ..."</span></div>
</div>
</div>
</div>
<!-- بخش ۸: چک‌لیست نهایی -->
<div class="section">
<div class="section-header">
<div class="section-number">۸</div>
<div class="section-title">
<h3>چک‌لیست نهایی اصلاحات</h3>
<div class="subtitle">Final Checklist</div>
</div>
</div>
<div class="section-content">
<table>
<thead>
<tr>
<th>ردیف</th>
<th>فایل</th>
<th>تغییر</th>
<th>اولویت</th>
</tr>
</thead>
<tbody>
<tr>
<td>۱</td>
<td><code>xIds/DI/XDIHelperExtension.cs</code></td>
<td>بازنویسی <code>AddXIdentityServerAuthentication</code> با <code>PolicyScheme</code></td>
<td style="color: var(--danger); font-weight: 700;">🔴 حیاتی</td>
</tr>
<tr>
<td>۲</td>
<td><code>xIds/DI/XDIHelperExtension.cs</code></td>
<td>اصلاح <code>AddXAuthorization</code> — افزودن XApiKey به LocalApi Policy</td>
<td style="color: var(--danger); font-weight: 700;">🔴 حیاتی</td>
</tr>
<tr>
<td>۳</td>
<td><code>xIds/Startup.cs</code></td>
<td>بررسی <code>addXApiKeyAuthorization: true</code> در <code>AddXSwagger</code></td>
<td style="color: var(--warning); font-weight: 700;">🟡 بررسی</td>
</tr>
<tr>
<td>۴</td>
<td><code>xIds/Providers/XApiKeyAuthenticationHandler.cs</code></td>
<td>تأیید صحت کد (نیاز به تغییر ندارد)</td>
<td style="color: var(--success); font-weight: 700;">🟢 تأیید</td>
</tr>
<tr>
<td>۵</td>
<td><code>xIdentityHelper/XAuthorizationHelper.cs</code></td>
<td>تأیید Policyهای ApiKey (نیاز به تغییر ندارد)</td>
<td style="color: var(--success); font-weight: 700;">🟢 تأیید</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- بخش ۹: جمع‌بندی -->
<div class="section">
<div class="section-header">
<div class="section-number">۹</div>
<div class="section-title">
<h3>جمع‌بندی و نکات پایانی</h3>
<div class="subtitle">Conclusion</div>
</div>
</div>
<div class="section-content">
<p>
ریشه اصلی مشکل تست XApiKey در Swagger، <strong>نحوه ثبت Authentication Schemes</strong>
و <strong>عدم استفاده از PolicyScheme هوشمند</strong> بود. با اعمال اصلاحات گام ۱ و ۲،
سیستم احراز هویت به‌صورت خودکار بر اساس Header درخواست، Scheme مناسب را انتخاب می‌کند
و Swagger UI می‌تواند به‌درستی با API Key احراز هویت نماید.
</p>
<div class="success-box">
<strong>🎯 نتیجه نهایی:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li>درخواست‌های دارای Header <code>X-Api-Key</code> → به <code>XApiKeyAuthenticationHandler</code> هدایت می‌شوند.</li>
<li>درخواست‌های دارای Header <code>Authorization: Bearer</code> → به <code>LocalApi</code> (IdentityServer) هدایت می‌شوند.</li>
<li>Swagger UI هر دو روش احراز هویت را پشتیبانی می‌کند.</li>
<li>Policyهای <code>ApiKeyAccess</code>, <code>ApiKeyReadAccess</code> و غیره به‌درستی کار می‌کنند.</li>
</ul>
</div>
<div class="highlight-box">
<strong>⚠️ نکته مهم:</strong>
پس از اعمال تغییرات، حتماً پروژه را <strong>Clean</strong> و <strong>Rebuild</strong> کنید
و سپس Swagger UI را با <strong>Ctrl+F5</strong> رفرش نمایید تا تغییرات Security Definition
به‌درستی اعمال شوند.
</div>
</div>
</div>
</div>
<footer class="document-footer">
<div class="footer-brand">تهیه شده توسط: هادی خزاعی اصل | شرکت فن آوران ساحر علم</div>
<div class="footer-divider"></div>
<div>تاریخ انتشار: ۲۰ مهر ۱۴۰۵ | نسخه: ۲.۰ | طبقه‌بندی: داخلی — فنی</div>
</footer>
</div>
</body>
</html>