Search code examples
c#.netoauth-2.0openid-connectsign-in-with-apple

Sign In With Apple .NET 'Invalid_client' error


I'm trying to set up Sign In With Apple on my .NET 7 Web App. I followed the guide by Scott Brady here: https://www.scottbrady91.com/openid-connect/implementing-sign-in-with-apple-in-aspnet-core

It reaches Apple OK, but when I have logged in, the callback responds with this error.

OpenIdConnectProtocolException: Message contains error: 'invalid_client', error_description: 'error_description is null', error_uri: 'error_uri is null'.

Googling hasn't helped much, other than I saw a post saying to wait 48 hours, which I have now done (not that that makes sense anyway).

Any idea whats been done wrong? Code below, replacing sensitive data.

Added to Startup.cs services.AddAuthentication

.AddOpenIdConnect("apple", async options =>
    {
        options.Authority = "https://appleid.apple.com"; // disco doc: https://appleid.apple.com/.well-known/openid-configuration

        options.ClientId = "com.rackemapp.applelogin"; // Service ID
        options.CallbackPath = "/signin-apple"; // corresponding to your redirect URI

        options.ResponseType = "code id_token"; // hybrid flow due to lack of PKCE support
        options.ResponseMode = "form_post"; // form post due to prevent PII in the URL
        options.UsePkce = false; // apple does not currently support PKCE (April 2021)
        options.DisableTelemetry = true;

        options.Scope.Clear(); // apple does not support the profile scope
        options.Scope.Add("openid");
        options.Scope.Add("email");
        options.Scope.Add("name");
        options.Events.OnAuthorizationCodeReceived = context =>
        {
            context.TokenEndpointRequest.ClientSecret = AppleTokenGenerator.CreateNewToken();
            return Task.CompletedTask;
        };
    });

Apple Token Generator

public static class AppleTokenGenerator
{
    public static string CreateNewToken()
    {
        const string iss = "[MyTeamId]"; // your account's team ID found in the dev portal
        const string aud = "https://appleid.apple.com";
        const string sub = "com.rackemapp.applelogin"; // same as client_id
        var now = DateTime.UtcNow;

        // contents of your .p8 file
        const string privateKey = "[MyKey]";
        var ecdsa = ECDsa.Create();
        ecdsa?.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out _);

        var handler = new JsonWebTokenHandler();
        return handler.CreateToken(new SecurityTokenDescriptor
        {
            Issuer = iss,
            Audience = aud,
            Claims = new Dictionary<string, object> { { "sub", sub } },
            Expires = now.AddMinutes(5), // expiry can be a maximum of 6 months - generate one per request or re-use until expiration
            IssuedAt = now,
            NotBefore = now,
            SigningCredentials = new SigningCredentials(new ECDsaSecurityKey(ecdsa), SecurityAlgorithms.EcdsaSha256)
        });
    }
}

The action calling the challenge

[AllowAnonymous]
public IActionResult AppleLogin()
{
    return Challenge("apple");
}

UPDATE Following Martin's comment, I explored a different angle following his blog article and sample application, and replacing my variables in still resulted in the same error. So I have pasted below screenshots of the AppleDeveloper console which might provide a clue, but it all looks Ok to me!

This shows the ServicesID setup, which gets used as the ClientID in the OAuth provider (1)

enter image description here

  • This shows the service is linked to a primary app (This uses a different bundle identifier of course) *

enter image description here

This shows the TeamID being used, in both the original post and Martin's link (2)

enter image description here

This shows the keyID which is present in the .p8 file (I know this is the right ID as if I change the name or the content of the file I get a different error)

enter image description here

Edit: I've been trying all sorts of methods to no avail. Is there any other documentation anyone can point me towards?


Solution

  • So after doing more digging I came across a post that suggested I pass in the KeyId to the Signing credentials.

    SigningCredentials = new SigningCredentials(new ECDsaSecurityKey(ecdsa){KeyId = keyId},
    SecurityAlgorithms.EcdsaSha256)
    ``