2

adopting from scottbrady91.com, I'm trying to have an Apple external authentication on our site. I've had Microsoft one working, but not the Apple one yet. The user is already directed to appleid.apple.com, but after authentication, it's returned to https://iluvrun.com/signin-apple (which is correct), but this isn't handled and so the user gets a 404 error.

To be honest I don't know how signin-facebook, signin-google or signin-oidc work, but they just do. So I have problems figuring out why signin-apple isn't being handled.

The site is built using ASP.NET Web Forms. Below is what I have at Startup.Auth.cs:

namespace ILR
{
    public partial class Startup {
        public void ConfigureAuth(IAppBuilder app)
        {
            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login")
            });

            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            app.UseOpenIdConnectAuthentication(
                new OpenIdConnectAuthenticationOptions("Apple")
                {
                    ClientId = "com.iluvrun.login",
                    Authority = "https://appleid.apple.com/auth/authorize",
                    SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                    RedirectUri = "https://iluvrun.com/signin-apple",
                    PostLogoutRedirectUri = "https://iluvrun.com",
                    Scope = "name email",
                    ResponseType = OpenIdConnectResponseType.Code,
                    ResponseMode = OpenIdConnectResponseMode.FormPost,
                    CallbackPath = PathString.FromUriComponent("/signin-apple"),
                    Configuration = new OpenIdConnectConfiguration
                    {
                        AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize",
                        TokenEndpoint = "https://appleid.apple.com/auth/token"
                    },
                    TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidIssuer = "https://appleid.apple.com",
                        IssuerSigningKey = new JsonWebKeySet(GetKeysAsync().Result).Keys[0]
                    },
                    Notifications = new OpenIdConnectAuthenticationNotifications
                    {
                        AuthorizationCodeReceived = (context) =>
                        {
                            context.TokenEndpointRequest.ClientSecret = TokenGenerator.CreateNewToken();

                            return Task.CompletedTask;
                        },
                        AuthenticationFailed = (context) =>
                        {
                            context.HandleResponse();
                            context.Response.Redirect("/Account/Login?errormessage=" + context.Exception.Message);

                            return Task.FromResult(0);
                        }
                    },
                    ProtocolValidator = new OpenIdConnectProtocolValidator
                    {
                        RequireNonce = false,
                        RequireStateValidation = false
                    }
                }
            );
        }

        private static async Task<string> GetKeysAsync()
        {
            string jwks = await new HttpClient().GetStringAsync("https://appleid.apple.com/auth/keys");

            return jwks;
        }
   }

    public static class TokenGenerator
    {
        public static string CreateNewToken()
        {
            const string iss = "CHM57Z5A6";
            const string aud = "https://appleid.apple.com";
            const string sub = "com.iluvrun.login";
            const string privateKey = "XXXX"; // contents of .p8 file
            CngKey cngKey = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);

            JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
            JwtSecurityToken token = handler.CreateJwtSecurityToken(
                issuer: iss,
                audience: aud,
                subject: new ClaimsIdentity(new List<Claim> { new Claim("sub", sub) }),
                expires: DateTime.UtcNow.AddMinutes(5),
                issuedAt: DateTime.UtcNow,
                notBefore: DateTime.UtcNow,
                signingCredentials: new SigningCredentials(new ECDsaSecurityKey(new ECDsaCng(cngKey)), SecurityAlgorithms.EcdsaSha256));

            return handler.WriteToken(token);
        }
    }
}

Does anyone have any clue what I miss to get this working?

Andry
  • 23
  • 4

1 Answers1

0

You are on the right track and your question helped me to quick start my own solution for Apple ID OpenIdConnect OWIN integration in my project. After finding your post here it took me quite long to fix all issues.
After using your code I've got the same 404 error.

  1. 404 error

This was due to unhandled exception in CreateNewToken() method, which wasn't able to generate valid token. In my case it was missing Azure configuration for my AppService more described in CngKey.Import on azure:

WEBSITE_LOAD_USER_PROFILE = 1

After setting this configuration in Azure I moved to next issue:

  1. Token endpoint wasn't called

This was due to missing configuration in OpenIdConnectAuthenticationOptions:

RedeemCode = true

This option trigger all the next processing of the authentication pipeline inside OpenIdConnect (TokenResponseReceived, SecurityTokenReceived, SecurityTokenValidated)

  1. AccountController.ExternalLoginCallback token processing issue

This was tricky. Because all you get is after calling

var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();

is getting:

loginInfo == null

So after reading lot of issue articles on this topic like OWIN OpenID provider - GetExternalLoginInfo() returns null and tries, the only final workaround was to add https://github.com/Sustainsys/owin-cookie-saver to my Startup.cs, which fixed problem of missing token cookies. Its marked as legacy, but it was my only option to fix this.

So final OpenIdConnect options config for my working solution is:

            var appleIdOptions = new OpenIdConnectAuthenticationOptions
        {
            AuthenticationType = "https://appleid.apple.com",
            ClientId = "[APPLE_CLIENT_ID_HERE]",
            Authority = "https://appleid.apple.com/auth/authorize",
            SignInAsAuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
            RedirectUri = "https://www.europeanart.eu/signin-apple",
            PostLogoutRedirectUri = "https://www.europeanart.eu",
            Scope = OpenIdConnectScope.Email,
            RedeemCode = true,
            ResponseType = OpenIdConnectResponseType.CodeIdToken,
            ResponseMode = OpenIdConnectResponseMode.FormPost,
            CallbackPath = PathString.FromUriComponent("/signin-apple"),
            Configuration = new OpenIdConnectConfiguration
            {
                AuthorizationEndpoint = "https://appleid.apple.com/auth/authorize",
                TokenEndpoint = "https://appleid.apple.com/auth/token"
            },
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidIssuer = "https://appleid.apple.com",
                ValidateIssuer = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKeys = new JsonWebKeySet(GetKeys()).Keys,
            },
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = (context) =>
                {
                    var clientToken = JwtTokenGenerator.CreateNewToken();

                    logger.LogInfo("Apple: clientToken generated");
                    context.TokenEndpointRequest.ClientSecret = clientToken;
                    logger.LogInfo("Apple: TokenEndpointRequest ready");

                    return Task.FromResult(0);
                },
                TokenResponseReceived = (context) =>
                {
                    logger.LogInfo("Apple: TokenResponseReceived");

                    return Task.FromResult(0);
                },
                SecurityTokenReceived = (context) =>
                {
                    logger.LogInfo("Apple: SecurityTokenReceived");

                    return Task.FromResult(0);
                },
                SecurityTokenValidated = (context) =>
                {
                    string userID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
                    logger.LogInfo("Apple: SecurityTokenValidated with userID=" + userID);

                    return Task.FromResult(0);
                },
                RedirectToIdentityProvider = (context) =>
                {
                    logger.LogInfo("Apple: RedirectToIdentityProvider");

                    if(context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                    {
                        logger.LogInfo("Apple: RedirectToIdentityProvider -> Authenticate()");
                    }
                    else if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Token)
                    {
                        logger.LogInfo("Apple: RedirectToIdentityProvider -> Token()");
                    }

                    return Task.FromResult(0);
                },
                AuthenticationFailed = (context) =>
                {
                    context.HandleResponse();
                    logger.LogError("Apple Authentication Failed.", context.Exception);
                    context.Response.Redirect("/Account/Login?errormessage=" + context.Exception.Message);

                    return Task.FromResult(0);
                }
            },
            ProtocolValidator = new OpenIdConnectProtocolValidator
            {
                RequireNonce = false,
                RequireStateValidation = false
            }
        };
Michal Vlk
  • 19
  • 2