B2C使用TP OpenID Connect的自定义策略-Idtokenaudienciencienciencion -MultiaPple方案

发布于 2025-01-28 05:25:16 字数 838 浏览 2 评论 0 原文

我有一个自定义策略,带有OpenID Connect技术配置文件呼叫授权和令牌端点,从元数据项目到我的自定义API中间件,该终点用于将其重定向到Apple Authenticathion端点/网站,因此我可以在我的自定义策略中处理一个多PPPLE解决方案,试图忽略<<< strong> client_id 和 idtokenaudience 。 Microsoft文档指出:

但不幸的是,文档是错误的,并且在成功获得苹果令牌并通过在苹果控制台上为该客户端配置的redirect_uri返回B2C 后,始终将其验证为b2c 我能够通过API中的授权端点。

一些B2C专家能否忽略自定义策略中的OpenID Connect TP中的IdTokenaudience?

Microsoft参考文档:

预先感谢!

I have a custom policy with an OpenId Connect Technical Profile calling authorize and token endpoints from metadata Items to my custom API middleware which is used to redirect to Apple authenticathion endpoint/website so i can handle a multiApple solution within my custom Policy trying to Ignore client_id and IdTokenAudience.
Microsoft documentation states:
enter image description here

But unfortunatelly the documentation is wrong and the TokenAudience is always validated after get sucessfully the Apple token and return the flow to B2C through the redirect_uri configured in Apple console for that clientId that I am able to pass through the Authorize endpoint in my API.

Can some B2C expert shed some light about ignore the IdTokenAudience in an OpenId Connect TP inside a Custom Policy?

Microsoft Reference document:

https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

|煩躁 2025-02-04 05:25:17

好的,我会回答我的问题。
如@JAS所述,由于安全验证,您无法摆脱客户端。因此,如果有人试图实施这种情况,我将解释我的方法:

  1. 使用client_id =“ myaudience”创建这样的技术配置文件,并使用元数据项目指向您的自定义OIDC Microservice Metadata。必须将B2C App客户端ID(B2C URL客户ID)传递,作为输入索赔以处理不同的Apple Client_id的索赔,具体取决于注册应用程序:

     &lt; technical -Profile id =“苹果ID”&gt;
       &lt;协议名称=“ OpenIdConnect” /&gt;
       &lt; metadata&gt;
         &lt; item key =“ metadata”&gt; https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx的
         &lt; item key =“ httpbinding”&gt; post&lt;/item&gt; 
         &lt; item key =“ response_types”&gt; code&lt;/item&gt;
         &lt; item key =“ usepoleCyInredirecturi”&gt; false&lt;/item&gt;
         &lt; item key =“ client_id”&gt; myaudience&lt;/item&gt;
       &lt;/metadata&gt;
       &lt; cryptographykeys&gt;
         &lt;键id =“ client_secret” storagereferenceId =“ b2c_1a_multiidp” /&gt;
       &lt;/cryptographykeys&gt;
       &lt; InputClaims&gt;
         &lt; InputClaim sipertypereferenceID =“ groupId” /&gt;
         &lt; InputClaim sipertypereferenceID =“ appid” pantersclaimType =“ clientid” defaultValue =“ {oidc:clientId}” /&gt;
       &lt;/inputClaims&gt;
       &lt; outputclaims&gt;
         &lt; outputClaim sipertypereferenceID =“ issueruserid” panteralclaimType =“ sub” /&gt;
         &lt; outputClaim sipertypereferenceID =“ email” panteralclaimType =“ email” /&gt;
         &lt; outputClaim sipertypereferenceID =“ fivenname” panteralclaimType =“ preferred_username” /&gt;
         &lt; outputClaim sipertypereferenceID =“ displayName” defaultValue =“苹果用户” /&gt;
         &lt; outputClaim sipertypereferenceID =“图片” phartsclaimType =“ picture” /&gt;
         &lt; outputClaim sipertypereferenceID =“ IdentityProvider” DefaultValue =“ Apple.com”始终usedefaultValue =“ true” /&gt;
       &lt;/outputclaims&gt;
     &lt;/technicalprofile&gt;
     
  2. 您的自定义Multi-IDP Microservice将通过querysize端口捕获corypoint,并应从querysize端口中捕获clientId并从存储与之相对应的Apple client_id,teamID,redirect_uri(比Apple Console相同),KeyID和发行器。

  3. 您 /授权应重定向到Apple身份验证页面,并提示您的凭据。< /p>

  4. 输入凭据后,请单击“继续”按钮您 /令牌端点,此时,您需要从密钥库中检索.p8 Secret(private键)以生成与代码一起使用的令牌在 /令牌体内。
    client_id,client_secret和keyID应用自定义存储的数据覆盖。

  1. 在制作令牌之后,并构建数据以发送到Apple/令牌端点,并使用其access_token,id_token,refresh_token检索Apple Final令牌。
  1. ​需要将所有这些数据退还给B2C,但受众将失败,因此您需要在通过配置的Redirect_uri传递到B2C之前募集新的ID_Token,您也可以将apple的索赔从Apple转移到新的伪造令牌。在您的B2C身份体验框架Wrok中,必须将用于签署新令牌的秘密密钥用于签名新令牌。
    有关更多信息,请参阅此文档: https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile
    该秘密在上面的技术配置文件中被称为CryptographyKey,为:b2c_1a_multiidp

​。

​愉快的编码!

2023年6月更新:

令牌生成器类:

public class TokenGenerator : ITokenGenerator
{
    private IConfiguration _config;
    public TokenGenerator(IConfiguration config)
    {
        _config = config;
    }
    public string GenerateGenericToken(string code, string issuer)
    {
        string newToken = string.Empty;

        Dictionary<string, object> claims = new Dictionary<string, object>();
        string sub = Guid.NewGuid().ToString();
        claims.Add("code", code);
        claims.Add("sub", sub);

        claims.Add("email", "[email protected]");
        claims.Add("email_verified", "true");
        claims.Add("auth_time", ((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds).ToString());
        newToken = GenerateToken(issuer, claims);
        return newToken;
    }

    public string GenerateToken(string issuer, Dictionary<string, object> claims)
    {
        var B2CEncodedPrivateKey = _config[ConfigParameter.B2CIDPPrivateSecret.ToString()];
        var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(B2CEncodedPrivateKey));
        var myAudience = "MyTPB2CCLIENTID";
        var tokenHandler = new JwtSecurityTokenHandler();

        Dictionary<string, object> header = new Dictionary<string, object>();
        header.Add("kid", "XXXXXXXXXXXXXXXXXXXXXXXX");
        header.Add("kty", "oct");
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, claims["sub"].ToString())
            }),
            Expires = DateTime.UtcNow.AddDays(7),
            Claims = claims,
            Issuer = issuer,
            Audience = myAudience,
            SigningCredentials = new SigningCredentials(mySecurityKey, SecurityAlgorithms.HmacSha256),
            AdditionalHeaderClaims = header

        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

Apple服务:

  public async Task<string> AuthorizeAsync(AuthorizeModelDTO authorizeModel)
        {
            _logger.LogEvent("GENERACION URI PROVIDER <APPLE>");
            string Uri = "";

            AppleClientData clientData = await _context.GetClientDataByProviderAsync(authorizeModel.clientId, authorizeModel.provider);

            Uri = GetAppleAuthorizeUri(authorizeModel.nonce, authorizeModel.state, clientData.TeamId, clientData.ProviderClientId, clientData.redirect_uri, authorizeModel.scope);
            //TODO: Check provider..
            _logger.LogEvent("FIN GENERACION URI PROVIDER <APPLE>");
            return Uri;
        }

        public Task<Dictionary<string, string>> CorrelateAsync(string provider, string code, string clientSecret)
        {
            _logger.LogEvent("GENERACION TOKEN GENERICO");

            Dictionary<string, string> result = new Dictionary<string, string>();  
            if (_config[ConfigParameter.B2CIDPPrivateSecret.ToString()] == clientSecret)
            {
                string issuer = "";
                if (provider.ToLower() == "Apple".ToLower())
                    issuer = "https://appleid.apple.com";

                result.Add("access_token", code);
                result.Add("token_type", "code");
                result.Add("expires_in", DateTime.Now.AddMinutes(30).ToLongTimeString());
                result.Add("refresh_token", "fakerefrshtoken");
                result.Add("id_token", _tokenGenerator.GenerateGenericToken(code, issuer));
            }
            else
            {
                result.Add("error", "invalid client_secret");
            }
            _logger.LogEvent("FIN GENERACION TOKEN GENERICO");
            return Task.FromResult(result);
        }

        public async Task<TokenData> TokenAsync(string clientId, string code, string CorrelationId, string provider, string hostName)
        {
            _logger.LogEvent($"PETICION TOKEN A TRAVES DE CODE <{code}>");

            Dictionary<string, string> result = new Dictionary<string, string>();
            var finalContent = "";
            try
            {
                var prov = provider.ToLower();

                _logger.LogInformation("Buscamos informacion del cliente segun el proveedor");
                //GET cosmos
                AppleClientData clientData = await _context.GetClientDataByProviderAsync(clientId, provider);

                if (clientData != null)
                {
                    _logger.LogInformation("ENCONTRAMOS informacion del cliente segun el proveedor");

                    var appClientId = clientData.ProviderClientId.Replace(".", "");

                    _logger.LogInformation("BUSCAMOS KV Secret");

                    var kvSecret = _config[prov + "-" + appClientId].ToString();

                    if(kvSecret != null && kvSecret != "")
                    {
                        _logger.LogInformation("ENCONTRAMOS KV Secret");
                        var secret = new CreateClientSecret
                        {
                            issuer = clientData.TeamId,
                            keyid = clientData.KeyId,
                            subject = clientData.ProviderClientId,
                            thumb = kvSecret
                        };

                        _logger.LogInformation("GENERAMOS Apple Token");

                        var secretToken = _appleTokenHandler.GenerateAppleToken(_config, "", secret);

                        if (!string.IsNullOrEmpty(secretToken))
                        {
                            _logger.LogInformation("GENERADO Apple Token");

                            
                            var data = _serviceAgent.GetAppleDataBody(clientData.ProviderClientId, secretToken, code, clientData.redirect_uri, CorrelationId);

                            _logger.LogInformation("LLAMADA a Apple /token");
                            finalContent = await _serviceAgent.GetAuthTokenApple(_config, data, "");

                            var r = JsonSerializer.Deserialize<AppleToken>(finalContent);
                            if (r != null)
                            {
                                //Validamos token
                                if (await _tokenValidator.CheckToken(r.id_token))
                                {
                                    TokenData dataToReturn = _appleTokenHandler.DecodeAppleToken(r.id_token);
                                    _logger.LogInformation($"////TOKEN DATA RESULT :");
                                    _logger.LogInformation(dataToReturn.ToString());
                                    _logger.LogEvent($"EXITO FIN PETICION TOKEN A TRAVES DE CODE");
                                    return dataToReturn;
                                }
                            }
                        }
                    } 
                }
            }
            catch (Exception)
            {
                _logger.LogEvent($"ERROR FIN PETICION TOKEN A TRAVES DE CODE");
                throw;
            }
            _logger.LogEvent($"SIN EXITO FIN PETICION TOKEN A TRAVES DE CODE");
            return null;
        }

        private string GetAppleAuthorizeUri(string nonce, string state, string teamId, string AppleclientId, string redirectUri, string scope)
        {
            return $"https://appleid.apple.com/auth/authorize?client_id={AppleclientId}&redirect_uri={redirectUri}&response_type=code&scope={scope}&response_mode=form_post&nonce={nonce}&token_issuer={teamId}&state=StateProperties%3D{state}";
        }

Ok, i will answer my question.
As stated by @Jas you cannot get rid of client_id because of security validations. So if someone is trying to implement such scenario, i will explain my approach:

  1. Create a Technical Profile like this with client_id="myaudience" and use METADATA item to point to your custom OIDC microservice metadata. A B2C app clientId (B2C URL clientId) must be passed as input claim to handle diferent Apple client_id's depending on registered App:

      <TechnicalProfile Id="AppleID">
       <Protocol Name="OpenIdConnect" />
       <Metadata>
         <Item Key="METADATA">https://xxxxxxxx.ngrok.io/metadata?provider=apple</Item>
         <Item Key="HttpBinding">POST</Item> 
         <Item Key="response_types">code</Item>
         <Item Key="UsePolicyInRedirectUri">false</Item>
         <Item Key="client_id">myaudience</Item>
       </Metadata>
       <CryptographicKeys>
         <Key Id="client_secret" StorageReferenceId="B2C_1A_MultiIDP" />
       </CryptographicKeys>
       <InputClaims>
         <InputClaim ClaimTypeReferenceId="groupId" />
         <InputClaim ClaimTypeReferenceId="appId" PartnerClaimType="clientId" DefaultValue="{OIDC:ClientId}" />
       </InputClaims>
       <OutputClaims>
         <OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="sub" />
         <OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="email" />
         <OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="preferred_username" />
         <OutputClaim ClaimTypeReferenceId="displayName" DefaultValue="Apple user" />
         <OutputClaim ClaimTypeReferenceId="picture" PartnerClaimType="picture" />
         <OutputClaim ClaimTypeReferenceId="identityProvider" DefaultValue="apple.com" AlwaysUseDefaultValue="true" />
       </OutputClaims>
     </TechnicalProfile>
    
  2. Your custom multi-IDP microservice will capture in the /authorize endpoint via queryString the clientId and should retrieve from a storage the Apple client_id, teamId, redirect_uri (same than Apple console), KeyId and Issuer corresponding to it.

  3. your /authorize should redirect to the Apple authentication page and will prompt for your credentials.

  4. After enter credentials and click on "continue" button your /token endpoint should be fired, at this point you need to retrieve the .p8 secret (Private Key) from a Key Vault to generate the token to be used with the code in the /token body.
    The client_id, client_secret and keyId should be overrided with your custom stored data.

enter image description here

  1. After craft the token and build the data to send to Apple /token endpoint, retrieve the Apple final token with its access_token, id_token, refresh_token ..

enter image description here

  1. At this point you need to return all this data to B2C but the audience will fail, so you need to recraft a new id_token before passing the control to B2C through the configured redirect_uri, also you can move the Claims coming from Apple to the new forged token. The secret key used to sign the new token must be configured before as a Policy Key (signature) in your B2C Identity Experience Framewrok.
    Refer to this doc for more info: https://learn.microsoft.com/en-us/azure/active-directory-b2c/openid-connect-technical-profile
    This secret is referenced as cryptographickey in the Technical Profile above as: B2C_1A_MultiIDP

enter image description here

  1. Return the data from your microservice /token endpoint to B2C:

enter image description here

Thats all! Happy coding!

Update Jun 2023:

Token Generator class:

public class TokenGenerator : ITokenGenerator
{
    private IConfiguration _config;
    public TokenGenerator(IConfiguration config)
    {
        _config = config;
    }
    public string GenerateGenericToken(string code, string issuer)
    {
        string newToken = string.Empty;

        Dictionary<string, object> claims = new Dictionary<string, object>();
        string sub = Guid.NewGuid().ToString();
        claims.Add("code", code);
        claims.Add("sub", sub);

        claims.Add("email", "[email protected]");
        claims.Add("email_verified", "true");
        claims.Add("auth_time", ((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds).ToString());
        newToken = GenerateToken(issuer, claims);
        return newToken;
    }

    public string GenerateToken(string issuer, Dictionary<string, object> claims)
    {
        var B2CEncodedPrivateKey = _config[ConfigParameter.B2CIDPPrivateSecret.ToString()];
        var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(B2CEncodedPrivateKey));
        var myAudience = "MyTPB2CCLIENTID";
        var tokenHandler = new JwtSecurityTokenHandler();

        Dictionary<string, object> header = new Dictionary<string, object>();
        header.Add("kid", "XXXXXXXXXXXXXXXXXXXXXXXX");
        header.Add("kty", "oct");
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.NameIdentifier, claims["sub"].ToString())
            }),
            Expires = DateTime.UtcNow.AddDays(7),
            Claims = claims,
            Issuer = issuer,
            Audience = myAudience,
            SigningCredentials = new SigningCredentials(mySecurityKey, SecurityAlgorithms.HmacSha256),
            AdditionalHeaderClaims = header

        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        return tokenHandler.WriteToken(token);
    }
}

Apple Service:

  public async Task<string> AuthorizeAsync(AuthorizeModelDTO authorizeModel)
        {
            _logger.LogEvent("GENERACION URI PROVIDER <APPLE>");
            string Uri = "";

            AppleClientData clientData = await _context.GetClientDataByProviderAsync(authorizeModel.clientId, authorizeModel.provider);

            Uri = GetAppleAuthorizeUri(authorizeModel.nonce, authorizeModel.state, clientData.TeamId, clientData.ProviderClientId, clientData.redirect_uri, authorizeModel.scope);
            //TODO: Check provider..
            _logger.LogEvent("FIN GENERACION URI PROVIDER <APPLE>");
            return Uri;
        }

        public Task<Dictionary<string, string>> CorrelateAsync(string provider, string code, string clientSecret)
        {
            _logger.LogEvent("GENERACION TOKEN GENERICO");

            Dictionary<string, string> result = new Dictionary<string, string>();  
            if (_config[ConfigParameter.B2CIDPPrivateSecret.ToString()] == clientSecret)
            {
                string issuer = "";
                if (provider.ToLower() == "Apple".ToLower())
                    issuer = "https://appleid.apple.com";

                result.Add("access_token", code);
                result.Add("token_type", "code");
                result.Add("expires_in", DateTime.Now.AddMinutes(30).ToLongTimeString());
                result.Add("refresh_token", "fakerefrshtoken");
                result.Add("id_token", _tokenGenerator.GenerateGenericToken(code, issuer));
            }
            else
            {
                result.Add("error", "invalid client_secret");
            }
            _logger.LogEvent("FIN GENERACION TOKEN GENERICO");
            return Task.FromResult(result);
        }

        public async Task<TokenData> TokenAsync(string clientId, string code, string CorrelationId, string provider, string hostName)
        {
            _logger.LogEvent(
quot;PETICION TOKEN A TRAVES DE CODE <{code}>");

            Dictionary<string, string> result = new Dictionary<string, string>();
            var finalContent = "";
            try
            {
                var prov = provider.ToLower();

                _logger.LogInformation("Buscamos informacion del cliente segun el proveedor");
                //GET cosmos
                AppleClientData clientData = await _context.GetClientDataByProviderAsync(clientId, provider);

                if (clientData != null)
                {
                    _logger.LogInformation("ENCONTRAMOS informacion del cliente segun el proveedor");

                    var appClientId = clientData.ProviderClientId.Replace(".", "");

                    _logger.LogInformation("BUSCAMOS KV Secret");

                    var kvSecret = _config[prov + "-" + appClientId].ToString();

                    if(kvSecret != null && kvSecret != "")
                    {
                        _logger.LogInformation("ENCONTRAMOS KV Secret");
                        var secret = new CreateClientSecret
                        {
                            issuer = clientData.TeamId,
                            keyid = clientData.KeyId,
                            subject = clientData.ProviderClientId,
                            thumb = kvSecret
                        };

                        _logger.LogInformation("GENERAMOS Apple Token");

                        var secretToken = _appleTokenHandler.GenerateAppleToken(_config, "", secret);

                        if (!string.IsNullOrEmpty(secretToken))
                        {
                            _logger.LogInformation("GENERADO Apple Token");

                            
                            var data = _serviceAgent.GetAppleDataBody(clientData.ProviderClientId, secretToken, code, clientData.redirect_uri, CorrelationId);

                            _logger.LogInformation("LLAMADA a Apple /token");
                            finalContent = await _serviceAgent.GetAuthTokenApple(_config, data, "");

                            var r = JsonSerializer.Deserialize<AppleToken>(finalContent);
                            if (r != null)
                            {
                                //Validamos token
                                if (await _tokenValidator.CheckToken(r.id_token))
                                {
                                    TokenData dataToReturn = _appleTokenHandler.DecodeAppleToken(r.id_token);
                                    _logger.LogInformation(
quot;////TOKEN DATA RESULT :");
                                    _logger.LogInformation(dataToReturn.ToString());
                                    _logger.LogEvent(
quot;EXITO FIN PETICION TOKEN A TRAVES DE CODE");
                                    return dataToReturn;
                                }
                            }
                        }
                    } 
                }
            }
            catch (Exception)
            {
                _logger.LogEvent(
quot;ERROR FIN PETICION TOKEN A TRAVES DE CODE");
                throw;
            }
            _logger.LogEvent(
quot;SIN EXITO FIN PETICION TOKEN A TRAVES DE CODE");
            return null;
        }

        private string GetAppleAuthorizeUri(string nonce, string state, string teamId, string AppleclientId, string redirectUri, string scope)
        {
            return 
quot;https://appleid.apple.com/auth/authorize?client_id={AppleclientId}&redirect_uri={redirectUri}&response_type=code&scope={scope}&response_mode=form_post&nonce={nonce}&token_issuer={teamId}&state=StateProperties%3D{state}";
        }
じее 2025-02-04 05:25:16

它被用作替代。

当不存在此元数据项目时,我们确保观众与预期的受众client_id匹配。

指定后,我们确保AUD索赔与您在元数据项目中所述的内容匹配。

这永远不允许关闭验证。

It’s being used as an override.

When this metadata item is not present, we make sure the audience matches the expected audience, client_id.

When it is specified, we make sure the aud claim matches what you state in the metadata item.

This never allows the verification to be turned off.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文