客户端密码或客户端证书
鉴于 Web 应用现在调用下游的 Web API,请提供 appsettings.json 文件中客户端密码或客户端证书。 你还可以添加一个节来指定:
- 下游 Web API 的 URL
- 调用 API 所需的范围
在下面的示例中,GraphBeta 节指定了这些设置。
{
  "AzureAd": {
    "Instance": "https://login.partner.microsoftonline.cn/",
    "ClientId": "[Enter_the_Application_Id_Here]",
    "TenantId": "common",
   // To call an API
   "ClientCredentials": [
    {
      "SourceType": "ClientSecret",
      "ClientSecret":"[Enter_the_Client_Secret_Here]"
    }
  ]
 },
 "GraphBeta": {
    "BaseUrl": "https://microsoftgraph.chinacloudapi.cn/beta",
    "Scopes": ["https://microsoftgraph.chinacloudapi.cn/user.read"]
    }
}
注意
你可建议一组客户端凭据,其中包括无凭据解决方案,例如 Azure Kubernetes 的工作负载联合身份验证。
先前版本的 Microsoft.Identity.Web 在单个属性“ClientSecret”中(而非“ClientCredentials”中)表示客户端密码。 这仍然支持向后兼容,但你不能同时使用“ClientSecret”属性和“ClientCredentials”集合。
 
你可以提供客户端证书,而不是客户端密码。 以下代码片段演示如何使用存储在 Azure Key Vault 中的证书。
{
  "AzureAd": {
    "Instance": "https://login.partner.microsoftonline.cn/",
    "ClientId": "[Enter_the_Application_Id_Here]",
    "TenantId": "common",
   // To call an API
   "ClientCredentials": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.cn",
        "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
      }
   ]
  },
  "GraphBeta": {
    "BaseUrl": "https://microsoftgraph.chinacloudapi.cn/beta",
    "Scopes": ["https://microsoftgraph.chinacloudapi.cn/user.read"]
  }
}
警告
如果忘记将 Scopes 更改为数组,则当尝试使用 IDownstreamApi 时,范围会显示为 null,并且 IDownstreamApi 会尝试匿名(未经身份验证)调用下游 API,这会导致 401/unauthenticated。
 
Microsoft.Identity.Web 提供了多种通过配置或代码描述证书的方法。 有关详细信息,请参阅 GitHub 上的 Microsoft.Identity.Web - 使用证书。
修改 Startup.cs 文件
Web 应用需要获取下游 API 的令牌。 可通过在 .EnableTokenAcquisitionToCallDownstreamApi() 后面添加 .AddMicrosoftIdentityWebApp(Configuration) 行来指定它。 此行公开 IAuthorizationHeaderProvider 服务,可在控制器和页面操作中使用该服务。 不过,正如你将在以下两个选项中看到的那样,该操作可以更简单地完成。 还需要在 Startup.cs 中选择令牌缓存实现(例如 .AddInMemoryTokenCaches()):
using Microsoft.Identity.Web;
public class Startup
{
  // ...
  public void ConfigureServices(IServiceCollection services)
  {
  // ...
  services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
          .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
            .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"https://microsoftgraph.chinacloudapi.cn/user.read" })
            .AddInMemoryTokenCaches();
   // ...
  }
  // ...
}
传递给 EnableTokenAcquisitionToCallDownstreamApi 的范围是可选项,使 Web 应用能够请求范围并在用户登录时向用户征求对这些范围的许可。 如果未指定范围,Microsoft.Identity.Web 会启用增量许可体验。
              Microsoft.Identity.Web 提供了两种从 Web 应用调用 Web API 的机制,而不要求你获取令牌。 选择哪种方式取决于你是要调用 Microsoft Graph 还是调用另一个 API。
选项 1:调用 Microsoft Graph
如果要调用 Microsoft Graph,则可通过 Microsoft.Identity.Web 在 API 操作中直接使用 (由 Microsoft Graph SDK 公开)。 若要公开 Microsoft Graph,请执行以下操作:
- 将 Microsoft.Identity.Web.GraphServiceClient NuGet 包添加到项目。 
- 在 Startup.cs 文件的 - .AddMicrosoftGraph()后面添加- .EnableTokenAcquisitionToCallDownstreamApi()。- .AddMicrosoftGraph()具有多个重写。 使用将配置部分作为参数的重写,代码变为:
 - using Microsoft.Identity.Web;
public class Startup
{
  // ...
  public void ConfigureServices(IServiceCollection services)
  {
  // ...
  services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
          .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
            .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"https://microsoftgraph.chinacloudapi.cn/user.read" })
               .AddMicrosoftGraph(Configuration.GetSection("GraphBeta"))
            .AddInMemoryTokenCaches();
   // ...
  }
  // ...
}
 
选项 2:调用下游 Web API,而不是 Microsoft Graph
如果要调用 API 而不是 Microsoft Graph,可通过 Microsoft.Identity.Web 在 API 操作中使用  接口。 若要使用此接口,请执行以下操作:
- 将 Microsoft.Identity.Web.DownstreamApi NuGet 包添加到项目中。 
- 在 Startup.cs 文件的 - .AddDownstreamApi()后面添加- .EnableTokenAcquisitionToCallDownstreamApi()。- .AddDownstreamApi()具有两个参数,如以下代码片段所示:
 - 
- 服务 (API) 的名称,用于在控制器操作中引用相应的配置
- 表示用于调用下游 Web API 的参数的配置部分。
 - using Microsoft.Identity.Web;
public class Startup
{
  // ...
  public void ConfigureServices(IServiceCollection services)
  {
  // ...
  services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
          .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
            .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"https://microsoftgraph.chinacloudapi.cn/user.read" })
               .AddDownstreamApi("MyApi", Configuration.GetSection("GraphBeta"))
            .AddInMemoryTokenCaches();
   // ...
  }
  // ...
}
 
总结
与 Web API 一样,你可以选择各种令牌缓存实现。 有关详细信息,请参阅 GitHub 上的 Microsoft.Identity.Web - 令牌缓存序列化。
下图显示 Microsoft.Identity.Web 的各种可能性及其对 Startup.cs 文件的影响:
              
               
              
              
            
客户端密码或客户端证书
鉴于 Web 应用现在调用下游的 Web API,请提供 appsettings.json 文件中客户端密码或客户端证书。 你还可以添加一个节来指定:
- 下游 Web API 的 URL
- 调用 API 所需的范围
在下面的示例中,GraphBeta 节指定了这些设置。
{
  "AzureAd": {
    "Instance": "https://login.partner.microsoftonline.cn/",
    "ClientId": "[Enter_the_Application_Id_Here]",
    "TenantId": "common",
   // To call an API
   "ClientCredentials": [
    {
      "SourceType": "ClientSecret",
      "ClientSecret":"[Enter_the_Client_Secret_Here]"
    }
  ]
 },
 "GraphBeta": {
    "BaseUrl": "https://microsoftgraph.chinacloudapi.cn/beta",
    "Scopes": ["https://microsoftgraph.chinacloudapi.cn/user.read"]
    }
}
注意
你可建议一组客户端凭据,其中包括无凭据解决方案,例如 Azure Kubernetes 的工作负载联合身份验证。
先前版本的 Microsoft.Identity.Web 在单个属性“ClientSecret”中(而非“ClientCredentials”中)表示客户端密码。 这仍然支持向后兼容,但你不能同时使用“ClientSecret”属性和“ClientCredentials”集合。
 
你可以提供客户端证书,而不是客户端密码。 以下代码片段演示如何使用存储在 Azure Key Vault 中的证书。
{
  "AzureAd": {
    "Instance": "https://login.partner.microsoftonline.cn/",
    "ClientId": "[Enter_the_Application_Id_Here]",
    "TenantId": "common",
   // To call an API
   "ClientCredentials": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.cn",
        "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
      }
   ]
  },
  "GraphBeta": {
    "BaseUrl": "https://microsoftgraph.chinacloudapi.cn/beta",
    "Scopes": ["https://microsoftgraph.chinacloudapi.cn/user.read"]
  }
}
警告
如果忘记将 Scopes 更改为数组,则当尝试使用 IDownstreamApi 时,范围会显示为 null,并且 IDownstreamApi 会尝试匿名(未经身份验证)调用下游 API,这会导致 401/unauthenticated。
 
Microsoft.Identity.Web 提供了多种通过配置或代码描述证书的方法。 有关详细信息,请参阅 GitHub 上的 Microsoft.Identity.Web - 使用证书。
Startup.Auth.cs
Web 应用需要获取下游 API 的令牌,Microsoft.Identity.Web 提供了两种用来从 Web 应用调用 Web API 的机制。 选择哪种方式取决于你是要调用 Microsoft Graph 还是调用另一个 API。
选项 1:调用 Microsoft Graph
如果要调用 Microsoft Graph,则可通过 Microsoft.Identity.Web 在 API 操作中直接使用 (由 Microsoft Graph SDK 公开)。 若要公开 Microsoft Graph,请执行以下操作:
- 将 Microsoft.Identity.Web.GraphServiceClient NuGet 包添加到项目。
- 将 .AddMicrosoftGraph()添加到 Startup.Auth.cs 文件中的服务集合。.AddMicrosoftGraph()具有多个重写。 使用将配置部分作为参数的重写,代码变为:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.OWIN;
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.IdentityModel.Validators;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin;
namespace WebApp
{
    public partial class Startup
    {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions());
            // Get an TokenAcquirerFactory specialized for OWIN
            OwinTokenAcquirerFactory owinTokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance<OwinTokenAcquirerFactory>();
            // Configure the web app.
            app.AddMicrosoftIdentityWebApp(owinTokenAcquirerFactory,
                                          updateOptions: options => {});
            // Add the services you need.
            owinTokenAcquirerFactory.Services
                .Configure<ConfidentialClientApplicationOptions>(options => 
                      { options.RedirectUri = "https://localhost:44326/"; })
                .AddMicrosoftGraph()
                .AddInMemoryTokenCaches();
            owinTokenAcquirerFactory.Build();
        }
    }
}
选项 2:调用下游 Web API,而不是 Microsoft Graph
如果要调用 API 而不是 Microsoft Graph,可通过 Microsoft.Identity.Web 在 API 操作中使用  接口。 若要使用此接口,请执行以下操作:
- 将 Microsoft.Identity.Web.DownstreamApi NuGet 包添加到项目中。
- 在 Startup.cs 文件的 .AddDownstreamApi()后面添加.EnableTokenAcquisitionToCallDownstreamApi()。.AddDownstreamApi()有两个参数:
- 服务 (API) 的名称:你可以在控制器操作中使用此名称来引用相应的配置
- 表示用于调用下游 Web API 的参数的配置部分。
 
代码如下:
  using Microsoft.Extensions.DependencyInjection;
  using Microsoft.Identity.Client;
  using Microsoft.Identity.Web;
  using Microsoft.Identity.Web.OWIN;
  using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
  using Microsoft.IdentityModel.Validators;
  using Microsoft.Owin.Security;
  using Microsoft.Owin.Security.Cookies;
  using Owin;
  namespace WebApp
  {
      public partial class Startup
      {
          public void ConfigureAuth(IAppBuilder app)
          {
              app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
              app.UseCookieAuthentication(new CookieAuthenticationOptions());
              // Get a TokenAcquirerFactory specialized for OWIN.
              OwinTokenAcquirerFactory owinTokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance<OwinTokenAcquirerFactory>();
              // Configure the web app.
              app.AddMicrosoftIdentityWebApp(owinTokenAcquirerFactory,
                                            updateOptions: options => {});
              // Add the services you need.
              owinTokenAcquirerFactory.Services
                  .Configure<ConfidentialClientApplicationOptions>(options => 
                        { options.RedirectUri = "https://localhost:44326/"; })
                  .AddDownstreamApi("Graph", owinTokenAcquirerFactory.Configuration.GetSection("GraphBeta"))
                  .AddInMemoryTokenCaches();
              owinTokenAcquirerFactory.Build();
          }
      }
  }
总结
你可以选择各种令牌缓存实现。 有关详细信息,请参阅 GitHub 上的 Microsoft.Identity.Web - 令牌缓存序列化。
下图显示 Microsoft.Identity.Web 的各种可能性及其对 Startup.cs 文件的影响:
              
               
              
              
            
本文以及下一篇文章中的代码示例都是从 ASP.NET Web 应用示例中提取的。 不妨参阅相应示例来获取完整的实现详细信息。
实现 Java 代码示例
本文以及下一篇文章中的代码示例都是从使用适用于 Java 的 MSAL 的 Web 应用示例调用 Microsoft Graph 的 Java Web 应用中提取的。
此示例当前让适用于 Java 的 MSAL 生成授权代码 URL,并处理导航到 Microsoft 标识平台的授权终结点。 也可以使用冲刺 (sprint) 安全机制来登录用户。 不妨参阅相应示例来获取完整的实现详细信息。
实现 Node.js 代码示例
本文中的代码示例及以下内容摘自调用 Microsoft Graph 的 Node.js 和 Express.js Web 应用程序(使用 MSAL Node 的 Web 应用示例)。
此示例当前让 MSAL Node 生成授权代码 URL,并处理导航到 Microsoft 标识平台的授权终结点。 如下所示:
    /**
     * Prepares the auth code request parameters and initiates the first leg of auth code flow
     * @param req: Express request object
     * @param res: Express response object
     * @param next: Express next function
     * @param authCodeUrlRequestParams: parameters for requesting an auth code url
     * @param authCodeRequestParams: parameters for requesting tokens using auth code
     */
    redirectToAuthCodeUrl(authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
        return async (req, res, next) => {
            // Generate PKCE Codes before starting the authorization flow
            const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
            // Set generated PKCE codes and method as session vars
            req.session.pkceCodes = {
                challengeMethod: 'S256',
                verifier: verifier,
                challenge: challenge,
            };
            /**
             * By manipulating the request objects below before each request, we can obtain
             * auth artifacts with desired claims. For more information, visit:
             * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationurlrequest
             * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationcoderequest
             **/
            req.session.authCodeUrlRequest = {
                ...authCodeUrlRequestParams,
                responseMode: msal.ResponseMode.FORM_POST, // recommended for confidential clients
                codeChallenge: req.session.pkceCodes.challenge,
                codeChallengeMethod: req.session.pkceCodes.challengeMethod,
            };
            req.session.authCodeRequest = {
                ...authCodeRequestParams,
                code: '',
            };
            try {
                const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
                res.redirect(authCodeUrlResponse);
            } catch (error) {
                next(error);
            }
        };
    }
 
Microsoft.Identity.Web.OWIN 将通过设置正确的 OpenID Connect 设置、订阅代码接收的事件和兑换代码来简化代码。 兑换授权代码不需要其他代码。 请参阅 Microsoft.Identity.Web 源代码,详细了解其原理。
AuthProvider 类中的 handleRedirect 方法处理从 Microsoft Entra ID 接收的授权代码。 如下所示:
    handleRedirect(options = {}) {
        return async (req, res, next) => {
            if (!req.body || !req.body.state) {
                return next(new Error('Error: response not found'));
            }
            const authCodeRequest = {
                ...req.session.authCodeRequest,
                code: req.body.code,
                codeVerifier: req.session.pkceCodes.verifier,
            };
            try {
                const msalInstance = this.getMsalInstance(this.msalConfig);
                if (req.session.tokenCache) {
                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
                }
                const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
                req.session.tokenCache = msalInstance.getTokenCache().serialize();
                req.session.idToken = tokenResponse.idToken;
                req.session.account = tokenResponse.account;
                req.session.isAuthenticated = true;
                const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state));
                res.redirect(state.successRedirect);
            } catch (error) {
                next(error);
            }
        }
    }
若要了解 Java 示例如何获取授权代码,请参阅用于登录用户的 Web 应用:代码配置。 在应用收到授权代码后,AuthFilter.java#L51-L56:
- 将授权代码委派给 AuthHelper.processAuthenticationCodeRedirect中的  方法。
- 调用 getAuthResultByAuthCode。
class AuthHelper {
  // Code omitted
  void processAuthenticationCodeRedirect(HttpServletRequest httpRequest, String currentUri, String fullUrl)
            throws Throwable {
  // Code omitted
  AuthenticationResponse authResponse = AuthenticationResponseParser.parse(new URI(fullUrl), params);
  // Code omitted
  IAuthenticationResult result = getAuthResultByAuthCode(
                    httpRequest,
                    oidcResponse.getAuthorizationCode(),
                    currentUri);
// Code omitted
  }
}
              getAuthResultByAuthCode 方法是在 AuthHelper.java#L176 中定义。 它创建 MSAL ConfidentialClientApplication,然后使用通过授权代码创建的 acquireToken() 来调用 AuthorizationCodeParameters。
   private IAuthenticationResult getAuthResultByAuthCode(
            HttpServletRequest httpServletRequest,
            AuthorizationCode authorizationCode,
            String currentUri) throws Throwable {
        IAuthenticationResult result;
        ConfidentialClientApplication app;
        try {
            app = createClientApplication();
            String authCode = authorizationCode.getValue();
            AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder(
                    authCode,
                    new URI(currentUri)).
                    build();
            Future<IAuthenticationResult> future = app.acquireToken(parameters);
            result = future.get();
        } catch (ExecutionException e) {
            throw e.getCause();
        }
        if (result == null) {
            throw new ServiceUnavailableException("authentication result was null");
        }
        SessionManagementHelper.storeTokenCacheInSession(httpServletRequest, app.tokenCache().serialize());
        return result;
    }
    private ConfidentialClientApplication createClientApplication() throws MalformedURLException {
        return ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.create(clientSecret)).
                authority(authority).
                build();
    }
若要了解 Python 示例如何获取授权代码,请参阅用于登录用户的 Web 应用:代码配置。
Microsoft 登录屏幕将授权代码发送到注册应用时指定的 /getAToken URL。 
              auth_response 路由处理该 URL,调用 auth.complete_login 来处理授权代码,然后返回错误或重定向到主页。
@app.route(app_config.REDIRECT_PATH)
def auth_response():
    result = auth.complete_log_in(request.args)
    if "error" in result:
        return render_template("auth_error.html", result=result)
    return redirect(url_for("index"))
有关此代码的完整上下文,请参阅 app.py。
 
ASP.NET Core 教程使用依赖关系注入,让你能够在应用的 Startup.cs 文件中确定令牌缓存实现。 Microsoft.Identity.Web 随附了令牌缓存序列化中所述的预生成令牌缓存序列化程序。 一个有意思的地方是,可以选择 ASP.NET Core 分布式内存缓存:
// Use a distributed token cache by adding:
    services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd")
            .EnableTokenAcquisitionToCallDownstreamApi(
                initialScopes: new string[] { "https://microsoftgraph.chinacloudapi.cn/user.read" })
            .AddDistributedTokenCaches();
// Then, choose your implementation.
// For instance, the distributed in-memory cache (not cleared when you stop the app):
services.AddDistributedMemoryCache();
// Or a Redis cache:
services.AddStackExchangeRedisCache(options =>
{
 options.Configuration = "localhost";
 options.InstanceName = "SampleInstance";
});
// Or even a SQL Server token cache:
services.AddDistributedSqlServerCache(options =>
{
 options.ConnectionString = _config["DistCache_ConnectionString"];
 options.SchemaName = "dbo";
 options.TableName = "TestCache";
});
有关令牌缓存提供程序的详细信息,另请参阅 Microsoft.Identity.Web 的令牌缓存序列化一文,以及 Web 应用教程的 ASP.NET Core Web 应用教程 | 令牌缓存部分。
ASP.NET 教程将使用依赖关系注入,让你能够在 Startup.Auth.cs 文件中为应用程序确定令牌缓存实现。 Microsoft.Identity.Web 随附了令牌缓存序列化中所述的预生成令牌缓存序列化程序。 一个有意思的地方是,可以选择 ASP.NET Core 分布式内存缓存:
var services = owinTokenAcquirerFactory.Services;
// Use a distributed token cache by adding:
services.AddDistributedTokenCaches();
// Then, choose your implementation.
// For instance, the distributed in-memory cache (not cleared when you stop the app):
services.AddDistributedMemoryCache();
// Or a Redis cache:
services.AddStackExchangeRedisCache(options =>
{
 options.Configuration = "localhost";
 options.InstanceName = "SampleInstance";
});
// Or even a SQL Server token cache:
services.AddDistributedSqlServerCache(options =>
{
 options.ConnectionString = _config["DistCache_ConnectionString"];
 options.SchemaName = "dbo";
 options.TableName = "TestCache";
});
有关令牌缓存提供程序的详细信息,另请参阅 Microsoft.Identity.Web 令牌缓存序列化一文,以及 Web 应用教程的 ASP.NET Core Web 应用教程 | 令牌缓存部分。
有关详细信息,请参阅 MSAL.NET 中的令牌缓存序列化。
MSAL Java 提供了用于序列化和反序列化令牌缓存的方法。 Java 示例处理来自会话的序列化,如 getAuthResultBySilentFlow 中的  方法所示:
IAuthenticationResult getAuthResultBySilentFlow(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
      throws Throwable {
  IAuthenticationResult result =  SessionManagementHelper.getAuthSessionObject(httpRequest);
  IConfidentialClientApplication app = createClientApplication();
  Object tokenCache = httpRequest.getSession().getAttribute("token_cache");
  if (tokenCache != null) {
      app.tokenCache().deserialize(tokenCache.toString());
  }
  SilentParameters parameters = SilentParameters.builder(
          Collections.singleton("https://microsoftgraph.chinacloudapi.cn/User.Read"),
          result.account()).build();
  CompletableFuture<IAuthenticationResult> future = app.acquireTokenSilently(parameters);
  IAuthenticationResult updatedResult = future.get();
  // Update session with latest token cache.
  SessionManagementHelper.storeTokenCacheInSession(httpRequest, app.tokenCache().serialize());
  return updatedResult;
}
              SessionManagementHelper中提供了  类的详细信息。
在 Node.js 示例中,应用会话用于存储令牌缓存。 使用 MSAL Node 缓存方法,在发出令牌请求之前读取会话中的令牌缓存,然后在令牌请求成功完成后进行更新。 如下所示:
    acquireToken(options = {}) {
        return async (req, res, next) => {
            try {
                const msalInstance = this.getMsalInstance(this.msalConfig);
                /**
                 * If a token cache exists in the session, deserialize it and set it as the 
                 * cache for the new MSAL CCA instance. For more, see: 
                 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
                 */
                if (req.session.tokenCache) {
                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
                }
                const tokenResponse = await msalInstance.acquireTokenSilent({
                    account: req.session.account,
                    scopes: options.scopes || [],
                });
                /**
                 * On successful token acquisition, write the updated token 
                 * cache back to the session. For more, see: 
                 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
                 */
                req.session.tokenCache = msalInstance.getTokenCache().serialize();
                req.session.accessToken = tokenResponse.accessToken;
                req.session.idToken = tokenResponse.idToken;
                req.session.account = tokenResponse.account;
                res.redirect(options.successRedirect);
            } catch (error) {
                if (error instanceof msal.InteractionRequiredAuthError) {
                    return this.login({
                        scopes: options.scopes || [],
                        redirectUri: options.redirectUri,
                        successRedirect: options.successRedirect || '/',
                    })(req, res, next);
                }
                next(error);
            }
        };
    }
在 Python 示例中,身份包使用全局 session 对象进行存储,处理令牌缓存。
Flask 已内置支持存储在 Cookie 中的会话,但由于身份 Cookie 的长度,本示例改为使用 Flask-会话包。 一切都在 app.py 中进行初始化:
import identity
import identity.web
import requests
from flask import Flask, redirect, render_template, request, session, url_for
from flask_session import Session
import app_config
app = Flask(__name__)
app.config.from_object(app_config)
Session(app)
auth = identity.web.Auth(
    session=session,
    authority=app.config["AUTHORITY"],
    client_id=app.config["CLIENT_ID"],
    client_credential=app.config["CLIENT_SECRET"],
)
由于 SESSION_TYPE="filesystem" 中的 app_config.py 设置,Flask-会话包使用本地文件系统存储会话。
在生产环境下,应使用在多个实例依然保留且部署你应用程序的设置,例如“sqlachemy”或“redis”。