.NET Framework C# OIDC将PKCE添加到亚马逊AWS认知

ilmyapht  于 2023-01-14  发布在  .NET
关注(0)|答案(1)|浏览(228)

我有三个应用程序,它们都使用不同的.NET方法(我不知道一个更好的词)。我有一个.NET核心3.1 Web应用程序,一个.NET框架4.8 MVC Web应用程序,和一个.NET框架4.6 Web窗体应用程序。
这三款应用都要求我使用PKCE(Proof Key for Code Exchange,用于代码交换的验证密钥)。这三款应用都使用Amazon AWS Cognito进行身份验证和授权。除了两款非.NET Core应用不使用PKCE之外,其他一切都运行良好,我需要它们使用PKCE。

  • 好的,我在这里的主要想法是,可能有一些“轻松”的方法,我可以通过Startup.Auth.cs文件或定义OIDC Auth* 之类的文件向.NET Framework应用程序添加PKCE支持。
    我的所作所为

我已经能够找到一些关于如何向站点添加PKCE的在线文章,但是它们都是向查询字符串添加必要位的手动机制。

NET Core这篇文章对我了解.NET Core应用程序的工作方式特别有用,但对其他两个应用程序没有帮助。总的来说,这个过程似乎分为以下步骤:
1.创建一个代码验证器,称之为验证器。
1.代码验证器和base64编码SHA 256。称之为挑战。
1.将其添加到我们发送到“authorize”端点的查询字符串中,沿着添加code_challenge_method,对于Cognito,该方法必须设置为S256。
1.将值为VERIFIER的Code_Verifier添加到我们发送到“token”端点的查询字符串中。
本质上,我们可以手动将以下内容添加到调用“authorize”端点的查询字符串中:“&代码挑战=挑战&代码挑战方法=S256”。
以及调用“token”端点的查询字符串:“&代码验证器=验证器”.(I'm pretty sure this token endpoint is a POST.)
我想就是这样。所以,我可以手动地将这些东西添加到这些调用中。然而,我更愿意让.NET框架神奇地为我做这些工作。

因此,我的总问题是:是否有办法修改.NET代码?我必须通过修改代码而不是手动添加来添加PKCE。

我已经搜索了Web、Microsoft站点,并且我已经摆弄了实际的代码,但是我没有找到任何在这些方法中添加PKCE的机制。

我的代码示例:

.NET框架Web窗体应用程序:

public void ConfigureAuth(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            CookieManager = new SystemWebCookieManager()
        });

        ConfigureIdentityProviders(app, DefaultAuthenticationTypes.ApplicationCookie);
    }

    private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
    {
        var saveTokens = true;
        var validateIssuer = true;
        var saveTokensValue = ConfigurationManager.AppSettings["Cognito.SaveTokens"];
        if (!string.IsNullOrEmpty(saveTokensValue))
        {
            saveTokens = bool.TryParse(saveTokensValue, out var outResult) && outResult;
        }
        var validateIssuerValue = ConfigurationManager.AppSettings["Cognito.TokenValidationParameters.ValidateIssuer"];
        if (!string.IsNullOrEmpty(validateIssuerValue))
        {
            validateIssuer = bool.TryParse(validateIssuerValue, out var outResult) && outResult;
        }

        app.UseCustomOidcAuthentication(
            new OpenIdConnectAuthenticationOptions
            {
                ClientId = ConfigurationManager.AppSettings["Cognito.ClientId"],
                ResponseType = ConfigurationManager.AppSettings["Cognito.ResponseType"],
                Authority = ConfigurationManager.AppSettings["Cognito.Authority"],
                MetadataAddress = ConfigurationManager.AppSettings["Cognito.MetadataAddress"],
                ClientSecret = ConfigurationManager.AppSettings["Cognito.ClientSecret"],
                RedirectUri = ConfigurationManager.AppSettings["Cognito.RedirectUri"],
                SaveTokens = saveTokens,
                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = ConfigurationManager.AppSettings["Cognito.TokenValidationParameters.NameClaimType"],
                    RoleClaimType = ConfigurationManager.AppSettings["Cognito.TokenValidationParameters.RoleClaimType"],
                    ValidateIssuer = validateIssuer
                },
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = async (context) =>
                    {
                        var removePortOnRedirectIdentifierValue = ConfigurationManager.AppSettings["Cognito.RemovePortOnRedirectToIdentityProvider"];
                        var convertResult = bool.TryParse(removePortOnRedirectIdentifierValue, out var removePortOnRedirectIdentifier);
                        if (removePortOnRedirectIdentifier && convertResult)
                        {
                            var builder = new UriBuilder(context.ProtocolMessage.RedirectUri)
                            {
                                Scheme = "https",
                                Port = -1
                            };

                            context.ProtocolMessage.RedirectUri = builder.ToString();
                        }
                    }
                },
                Scope = ConfigurationManager.AppSettings["Cognito.Scope"],
                SignInAsAuthenticationType = signInAsType
            }
        );
    }

我的.NET框架MVC应用程序:

public void ConfigureAuth(IAppBuilder app)
{
    var loginPath = AuthorizationSettings.Instance.LoginPath;
    if (string.IsNullOrEmpty(loginPath))
        throw new ArgumentNullException("No value specified for EiHubSettings LoginPath");

    // Configure the db context, user manager and signin manager to use a single instance per request
    app.CreatePerOwinContext(SecurityDbContext.Create);
    app.CreatePerOwinContext<SecurityUserManager>(SecurityUserManager.Create);
    app.CreatePerOwinContext<SecurityRoleManager>(SecurityRoleManager.Create);
    app.CreatePerOwinContext<SecuritySignInManager>(SecuritySignInManager.Create);
    
    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a third party login provider
    // Configure the sign in cookie
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString(loginPath),
        CookieManager = new SystemWebCookieManager(),
        ExpireTimeSpan = TimeSpan.FromSeconds(
            Convert.ToInt32(ConfigurationManager.AppSettings["Cognito.CookieLifetimeInSeconds"])),
        Provider = new CookieAuthenticationProvider
        {
            // Enables the application to validate the security stamp when the user logs in.
            // This is a security feature which is used when you change a password or add an external login to your account.  
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<SecurityUserManager, SecurityUser, int>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentityCallback: (manager, user) => user.CreateIdentityAsync(manager),
                getUserIdCallback: (user) => user.GetUserId<int>())
        }
    });            
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
    app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

    // Enables the application to remember the second login verification factor such as phone or email.
    // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
    // This is similar to the RememberMe option when you log in.
    app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

    if(AppSettingsConfigSingleton.UsesCognito)
    {
        ConfigureIdentityProviders(app, DefaultAuthenticationTypes.ApplicationCookie);
    }
}

private void ConfigureIdentityProviders(IAppBuilder app, string signInAsType)
{
    var saveTokens = true;
    var validateIssuer = true;
    var saveTokensValue = ConfigurationManager.AppSettings["Cognito.SaveTokens"];
    if (!string.IsNullOrEmpty(saveTokensValue))
    {
        saveTokens = bool.TryParse(saveTokensValue, out var outResult) && outResult;
    }
    var validateIssuerValue = ConfigurationManager.AppSettings["Cognito.TokenValidationParameters.ValidateIssuer"];
    if (!string.IsNullOrEmpty(validateIssuerValue))
    {
        validateIssuer = bool.TryParse(validateIssuerValue, out var outResult) && outResult;
    }
    
    app.UseCustomOidcAuthentication(
        new OpenIdConnectAuthenticationOptions
        {
            ClientId = ConfigurationManager.AppSettings["Cognito.ClientId"],
            ResponseType = ConfigurationManager.AppSettings["Cognito.ResponseType"],
            Authority = ConfigurationManager.AppSettings["Cognito.Authority"],
            MetadataAddress = ConfigurationManager.AppSettings["Cognito.MetadataAddress"],
            ClientSecret = ConfigurationManager.AppSettings["Cognito.ClientSecret"],
            RedirectUri = ConfigurationManager.AppSettings["Cognito.RedirectUri"],
            SaveTokens = saveTokens,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                SecurityTokenValidated = context =>
                {
                    var redirectUri = "/account/openidlogincallback";
                    if (!string.IsNullOrEmpty(context.AuthenticationTicket.Properties.RedirectUri))
                    {
                        redirectUri += $"?returnUrl={context.AuthenticationTicket.Properties.RedirectUri}";
                    }
                    context.AuthenticationTicket.Properties.RedirectUri = redirectUri;

                    return Task.FromResult(0);
                },
                RedirectToIdentityProvider = context =>
                {
                    var removePortOnRedirectIdentifierValue = ConfigurationManager.AppSettings["Cognito.RemovePortOnRedirectToIdentityProvider"];
                    var convertResult = bool.TryParse(removePortOnRedirectIdentifierValue, out var removePortOnRedirectIdentifier);
                    if (removePortOnRedirectIdentifier && convertResult)
                    {
                        var builder = new UriBuilder(context.ProtocolMessage.RedirectUri)
                        {
                            Scheme = "https", Port = -1
                        };

                        context.ProtocolMessage.RedirectUri = builder.ToString();
                    }

                    return Task.FromResult(0);
                }
            },
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = ConfigurationManager.AppSettings["Cognito.TokenValidationParameters.NameClaimType"],
                RoleClaimType = ConfigurationManager.AppSettings["Cognito.TokenValidationParameters.RoleClaimType"],
                ValidateIssuer = validateIssuer
            },
            Scope = ConfigurationManager.AppSettings["Cognito.Scope"],
            SignInAsAuthenticationType = signInAsType
        }
    );
}

NET Core 3.1应用程序(是的,我知道我们需要升级到.NET 6)有一个“内置”的机制来添加PKCE(这让我困惑了很长一段时间,直到我看到这个--这个选项在我的代码中没有显式设置,但正如您所看到的,它在默认情况下是打开的):

因此,我希望. NETFramework中也有类似的东西

s4n0splo

s4n0splo1#

StartUp类中:

//  Referred to in CustomOidcHandler
public const string CODE_VERIFIER = "code_verifier";

private const string _CODE_CHALLENGE = "code_challenge";
private const string _CODE_CHALLENGE_METHOD = "code_challenge_method";
private const string _CODE_CHALLENGE_METHOD_S256 = "S256";

ConfigureIdentityProviders中在Startup

RedirectToIdentityProvider = (context) =>
                {
                    var removePortOnRedirectIdentifierValue = ConfigurationManager.AppSettings["Cognito.RemovePortOnRedirectToIdentityProvider"];
                    var convertResult = bool.TryParse(removePortOnRedirectIdentifierValue, out var removePortOnRedirectIdentifier);
                    if (removePortOnRedirectIdentifier && convertResult)
                    {
                        var builder = new UriBuilder(context.ProtocolMessage.RedirectUri)
                        {
                            Scheme = "https",
                            Port = -1
                        };

                        context.ProtocolMessage.RedirectUri = builder.ToString();
                    }

                    //
                    //  We need the code_verifier value passed to the CustomOidcHandler
                    //  so that it can be added to the POST to the /token endpoint
                    //
                    string codeVerifier = GenerateCodeVerifier();
                    HttpCookie cookie = new HttpCookie(CODE_VERIFIER);
                    cookie.Value = codeVerifier;
                    HttpContext.Current.Response.Cookies.Add(cookie);

                    //
                    //  Add the code_challenge to the GET request to Cognito
                    //
                    //  Methodology found here: https://github.com/IdentityServer/IdentityServer4/issues/4874
                    //
                    string codeChallenge = GenerateCodeChallenge(codeVerifier);
                    context.ProtocolMessage.SetParameter(_CODE_CHALLENGE, codeChallenge);
                    context.ProtocolMessage.SetParameter(_CODE_CHALLENGE_METHOD, _CODE_CHALLENGE_METHOD_S256);

                    return Task.CompletedTask;
                }

在我的CustomOidcHandler文件中,GetTokens方法:

string codeVerifier = Request != null && Request.Cookies != null && Request.Cookies[Startup.CODE_VERIFIER] != null ? Request.Cookies[Startup.CODE_VERIFIER] : string.Empty;
    Dictionary<string, string> post = new Dictionary<string, string>
    {
        {"client_id", options.ClientId},
        {"client_secret", options.ClientSecret},
        {"grant_type", "authorization_code"},
        {"code", authorizationCode},
        {"redirect_uri", options.RedirectUri},
        {Startup.CODE_VERIFIER, codeVerifier}
    };

因此,唯一的方法是“手动”添加它。

相关问题