Bitzsoft.Integrations.AzureAD 1.0.0-alpha.10

This is a prerelease version of Bitzsoft.Integrations.AzureAD.
dotnet add package Bitzsoft.Integrations.AzureAD --version 1.0.0-alpha.10
                    
NuGet\Install-Package Bitzsoft.Integrations.AzureAD -Version 1.0.0-alpha.10
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Bitzsoft.Integrations.AzureAD" Version="1.0.0-alpha.10" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Bitzsoft.Integrations.AzureAD" Version="1.0.0-alpha.10" />
                    
Directory.Packages.props
<PackageReference Include="Bitzsoft.Integrations.AzureAD" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Bitzsoft.Integrations.AzureAD --version 1.0.0-alpha.10
                    
#r "nuget: Bitzsoft.Integrations.AzureAD, 1.0.0-alpha.10"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Bitzsoft.Integrations.AzureAD@1.0.0-alpha.10
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Bitzsoft.Integrations.AzureAD&version=1.0.0-alpha.10&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Bitzsoft.Integrations.AzureAD&version=1.0.0-alpha.10&prerelease
                    
Install as a Cake Tool

Bitzsoft.Integrations.AzureAD

Azure AD / Microsoft Entra ID 集成客户端 — OAuth 2.0 PKCE 流程、OIDC Token 验证、Graph API 组查询、用户自动供应。

功能特性

  • OAuth 2.0 PKCE 授权码流程:完整实现 Auth Code Flow + PKCE,支持 MVC / Web App 场景
  • OIDC Token 验证:区分 ID Token(登录验证)和 Access Token(API 鉴权)两类场景
  • ASP.NET Core JwtBearer 集成:一键注册后 [Authorize] 直接生效
  • Graph API 组查询:通过共享 Graph/REST transport 与 Client Credentials 应用身份查询用户直接所属组 + 角色映射
  • 用户自动供应:外部登录绑定 + 邮箱匹配
  • JWKS 自动轮换:基于 ConfigurationManager 自动刷新签名密钥
  • 登录与 Graph 租户解耦:登录可使用 common / organizations, Graph 应用身份必须绑定具体目录租户
  • 国家云/代理端点:Graph v1.0 服务根、身份实例和 resource audience 均可配置
  • 第三方请求日志:交互式 OAuth/OIDC 默认挂载 RequestLogging; Graph 共享 HTTP 管道可由宿主统一挂载

支持 net5.0net8.0net10.0

安装

.NET CLI

dotnet add package Bitzsoft.Integrations.AzureAD

PackageReference

<PackageReference Include="Bitzsoft.Integrations.AzureAD" Version="1.0.0" />

配置

appsettings.json

{
  "AzureAD": {
    "Enabled": true,
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "common",
    "GraphTenantId": "your-concrete-directory-tenant-id",
    "GraphApiBaseUrl": "https://graph.microsoft.com/v1.0",
    "ClientId": "your-client-id",
    "ClientSecret": "your-client-secret",
    "CallbackPath": "/api/callback/azuread",
    "Audience": "api://your-client-id",
    "AllowedTenantIds": [
      "00000000-0000-0000-0000-000000000000"
    ],
    "TokenClockSkew": "00:05:00",
    "Scopes": [ "openid", "profile", "email" ]
  }
}
配置项 说明 默认值
Enabled 是否启用 false
Instance Azure AD 实例地址 https://login.microsoftonline.com/
TenantId 租户 ID,多租户使用 common common
GraphTenantId Graph 应用身份使用的具体租户;为空时回退到 TenantId
GraphApiBaseUrl Microsoft Graph v1.0 服务根 https://graph.microsoft.com/v1.0
GraphResourceAudience 自定义代理的 OAuth resource audience Graph 服务根的 origin
GraphRequestTimeout Graph 单次操作总超时 00:00:30
ClientId 应用程序(客户端)ID --
ClientSecret 客户端密钥 --
CallbackPath OAuth 回调路径 /api/callback/azuread
Audience Access Token 受众(SPA 场景必填) 默认 fallback 为 ClientId
AllowedTenantIds 允许登录/调用 API 的租户 GUID 白名单;空表示接受 authority 范围内任意已验证租户 []
TokenClockSkew Token 生命周期允许的时钟偏差,范围 0~15 分钟 00:05:00
Scopes OAuth 请求的 Scope ["openid","profile","email"]

注册服务

核心注册(必选)

using Bitzsoft.Integrations.AzureAD.DependencyInjection;

// 注册核心服务(OAuth + Token 验证)
services.AddBitzsoftAzureAD(options =>
{
    options.Instance = "https://login.microsoftonline.com/";
    options.TenantId = "common";
    options.GraphTenantId = "your-concrete-directory-tenant-id";
    options.ClientId = "your-client-id";
    options.ClientSecret = "your-client-secret";
    options.Audience = "api://your-client-id";
    options.AllowedTenantIds =
    [
        "00000000-0000-0000-0000-000000000000"
    ];
});

// 或从 IConfiguration 绑定
services.AddBitzsoftAzureAD(configuration.GetSection("AzureAD").Bind);

ASP.NET Core JwtBearer 集成(可选)

// 注册后 [Authorize] 直接生效,User.Identity 即为 AAD 用户
services.AddBitzsoftAzureADJwtBearer();

JwtBearer 与手工 ValidateIdTokenAsync / ValidateAccessTokenAsync 使用同一套 严格验证策略:签名、audience、生命周期、tid、租户化 issuer 和签名密钥 issuer 均必须通过。MapInboundClaims 固定为 false,业务代码可直接读取 tidoid 等原始 Entra claim。

Graph API 组查询(可选)

services.AddBitzsoftAzureADGraph()
    .WithGroupRoleMapping<SqlGroupRoleMappingRepository>();

Graph API 使用 Client Credentials(应用身份)。当前 API 读取用户的直接 组成员关系,不包含嵌套组传递关系;应用需授予满足该接口的 Application permission(通常为 Directory.Read.All)并完成管理员同意。不能把 commonorganizationsconsumers 用作应用身份目录租户。

中国版示例:

options.Instance = "https://login.chinacloudapi.cn/";
options.GraphTenantId = "your-concrete-directory-tenant-id";
options.GraphApiBaseUrl =
    "https://microsoftgraph.chinacloudapi.cn/v1.0";

用户自动供应(可选)

services.AddBitzsoftAzureADProvisioning()
    .WithExternalLogin<SqlExternalLoginRepository>()
    .WithUserLookup<SqlUserLookupRepository>();

第三方请求日志

内置 Bitzsoft.Integrations.RequestLogging 出站请求记录管道,默认使用 NullRequestLogStore 不持久化。交互式 OAuth/OIDC 管道由 AddBitzsoftAzureAD 自动挂载;Graph Adapter 复用共享 REST HTTP 管道,不再重复挂载一套 handler。

using Bitzsoft.Integrations.Rest;

// ① 默认:启用记录管道但不持久化(日志丢弃)
services.AddBitzsoftAzureAD(options => { /* ... */ });

// ② 持久化:宿主注册 IRequestLogStore 实现
services.AddRequestLogging<MyRequestLogStore>(opts =>
{
    opts.MaxInMemoryBodyBytes = 64 * 1024; // 仅控制内存/加密临时文件切换,不截断正文
    opts.SensitiveFields.Add("mySecret");
});
services.AddBitzsoftAzureAD(options => { /* ... */ });

// ③ 如需记录 Graph,共享 REST 管道只挂载一次
services.AddHttpClient(RestConstants.HttpClientName)
    .AddRequestLogging("MicrosoftGraph");
services.AddBitzsoftAzureADGraph();

MaxLoggedBodyLength 默认是 null,不会强制限制日志正文大小;兆级第三方 response 仍可完整保存。只有宿主显式设置该选项时才截断。

OAuth Token 交换是例外:授权码、PKCE verifier、Client Secret 与返回的各类 Token 整段都属于凭据,因此连接器会显式调用 SuppressRequestAndResponseBodiesFromLogging(),只保留端点、状态码、耗时等 审计元数据。这个安全分类不会改变其他请求的正文记录策略,更不会给普通 response 增加强制大小上限。

使用示例

MVC / Web App — OAuth 登录流程

using Bitzsoft.Integrations.AzureAD.Interfaces;

public class AccountController : Controller
{
    private readonly IAzureADClient _aadClient;

    public AccountController(IAzureADClient aadClient)
    {
        _aadClient = aadClient;
    }

    // Step 1: 生成授权 URL
    public IActionResult Login()
    {
        var codeVerifier = _aadClient.GenerateCodeVerifier();
        var codeChallenge = _aadClient.GenerateCodeChallenge(codeVerifier);
        var nonce = _aadClient.GenerateNonce();
        var state = Guid.NewGuid().ToString("N");

        HttpContext.Session.SetString("pkce_verifier", codeVerifier);
        HttpContext.Session.SetString("nonce", nonce);
        HttpContext.Session.SetString("state", state);

        var authUrl = _aadClient.GetAuthorizationUrl(
            Url.Action("Callback", null, null, Request.Scheme),
            state, codeChallenge, nonce);

        return Redirect(authUrl);
    }

    // Step 2: 处理回调
    public async Task<IActionResult> Callback(string code, string state)
    {
        var codeVerifier = HttpContext.Session.GetString("pkce_verifier");
        var expectedNonce = HttpContext.Session.GetString("nonce");

        var tokenResponse = await _aadClient.ExchangeCodeForTokenAsync(
            code, Url.Action("Callback", null, null, Request.Scheme), codeVerifier!);

        if (tokenResponse?.IdToken is null)
            return BadRequest("Token exchange failed");

        var userInfo = await _aadClient.ValidateIdTokenAsync(
            tokenResponse.IdToken, expectedNonce);

        if (userInfo is null)
            return BadRequest("ID Token validation failed");

        // 使用 userInfo 登录系统
        return RedirectToAction("Index", "Home");
    }
}

SPA / Mobile App — Access Token 验证

using Bitzsoft.Integrations.AzureAD.Interfaces;

[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
    private readonly IAzureADClient _aadClient;

    public AuthController(IAzureADClient aadClient)
    {
        _aadClient = aadClient;
    }

    [HttpPost("validate")]
    public async Task<IActionResult> ValidateToken()
    {
        var accessToken = Request.Headers["Authorization"]
            .ToString().Replace("Bearer ", "");

        var userInfo = await _aadClient.ValidateAccessTokenAsync(accessToken);

        if (userInfo is null)
            return Unauthorized();

        return Ok(userInfo);
    }
}

使用 [Authorize](需注册 JwtBearer 扩展)

using Microsoft.AspNetCore.Authorization;

[ApiController]
[Route("api/[controller]")]
public class ApiController : ControllerBase
{
    [HttpGet("profile")]
    [Authorize(AuthenticationSchemes = "AzureADJwtBearer")]
    public IActionResult GetProfile()
    {
        var oid = User.FindFirst("oid")?.Value;
        var name = User.FindFirst("name")?.Value;
        return Ok(new { ObjectId = oid, Name = name });
    }
}

Graph API — 查询用户组并匹配角色

using Bitzsoft.Integrations.AzureAD.Interfaces;

public class RoleService
{
    private readonly IAzureADGraphClient _graphClient;

    public RoleService(IAzureADGraphClient graphClient)
    {
        _graphClient = graphClient;
    }

    public async Task<List<string>> GetUserRolesAsync(
        string tenantId, string userObjectId)
    {
        return await _graphClient.GetMatchedRoleIdsAsync(tenantId, userObjectId);
    }
}

Graph 分页过程中任何一页失败都会抛出 AzureADException,不会把已读取的 部分组伪装成成功结果;异常不包含供应商响应正文、令牌或用户标识。

核心类型一览

类型 说明
IAzureADClient 核心 OAuth + Token 验证接口
IAzureADGraphClient Graph API 直接组查询兼容接口(Client Credentials)
IAzureADProvisioningClient 用户自动供应接口
AzureADOptions 配置模型
AADTokenResponse OAuth 令牌响应
AADUserInfo 用户信息
AADGroupInfo 组信息
GroupRoleMapping 组-角色映射
ProvisioningResult 供应结果
AzureADException 自定义异常

ID Token vs Access Token

场景 Token 类型 验证方法 Audience
MVC/Web App 登录 ID Token ValidateIdTokenAsync ClientId
SPA/Mobile App 调用 API Access Token ValidateAccessTokenAsync api://xxx
ASP.NET Core [Authorize] Access Token 自动(中间件) api://xxx

多租户说明

TenantId 设为 commonorganizations 时:

  • issuer 验证始终开启;验证器要求 Token 恰好包含一个规范 GUID 格式的 tid,把 OIDC metadata 中的 {tenantid} 替换为该值后与 Token issuer 做精确匹配;
  • issuer 路径中的租户段必须与 tid 一致,并同时校验 audience、签名、 生命周期及签名密钥 issuer;支持的较新目标框架还会校验签名密钥云实例;
  • 建议生产 ToB 应用配置 AllowedTenantIds 白名单。若留空,技术上允许 authority 范围内任意通过密码学验证的租户,宿主仍必须在业务层执行租户 入驻、停用、授权范围和数据隔离检查;
  • state 与 nonce 由宿主保存为一次性会话值并在回调时核对。连接器对 nonce 使用常量时间比较,但不能替代宿主的 state/nonce 存储和消费逻辑。

授权 URL、Token 交换和登出入口会拒绝非 HTTPS 重定向(loopback HTTP 除外)、不合规 PKCE、空值、控制字符和超长 Token。无 Client Secret 的 Public Client + PKCE 场景不会发送空的 client_secret 字段。

依赖

  • Bitzsoft.Integrations.Compatibility — 基础工具库
  • Bitzsoft.Integrations.MicrosoftGraph — 共享 Graph v1.0 transport
  • Bitzsoft.Integrations.Rest — OAuth、HTTP 生命周期与结构化错误底座
  • Microsoft.Extensions.Http — IHttpClientFactory
  • Microsoft.Extensions.Options — 选项模式
  • Microsoft.IdentityModel.Protocols.OpenIdConnect — OIDC 协议
  • Microsoft.IdentityModel.Validators — Entra 签名密钥 issuer / 云实例校验
  • System.IdentityModel.Tokens.Jwt — JWT Token 处理
  • Microsoft.AspNetCore.Authentication.JwtBearer — ASP.NET Core JwtBearer 集成(可选)

相关包

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-alpha.10 33 7/26/2026
1.0.0-alpha.9 55 7/12/2026
1.0.0-alpha.8 162 7/1/2026
1.0.0-alpha.7 324 6/16/2026
1.0.0-alpha.6 67 6/16/2026
1.0.0-alpha.5 61 6/14/2026
1.0.0-alpha.4 56 7/1/2026
1.0.0-alpha.3 62 6/7/2026
1.0.0-alpha.2 64 5/29/2026