Accedia / appleauth-net

AppleAuth.NET is a simple library that facilitates the implementation of "Sign in with Apple" for .NET applications.
MIT License
47 stars 26 forks source link

JWT Token Generation issue results in invalid_client error #29

Closed SasLuca closed 1 year ago

SasLuca commented 1 year ago

For some reason after December 2022, using this library stopped working for me.

After extensive debugging it seems like for some reason Apple servers don't like tokens generated using .net

I extracted the code from GetAuthorizationToken to debug and experiment on it. Even tho the generated JWT seems to completely fine and appropriate when checking it on https://jwt.io, I get an invalid_client error.

I discovered the issue is the JWT token generation because if I generate it using some simple nodejs code it works.

Consider this code:

var restClient = new AppleRestClient();
var appleClientSecret = new TokenGenerator().GenerateAppleClientSecret(privateKey, appleOAuthSettings.TeamId, appleOAuthSettings.ClientId, appleOAuthSettings.KeyId);
var requestMessage = restClient.GenerateRequestMessage("authorization_code", authorizationCode, appleClientSecret, appleOAuthSettings.ClientId, appleOAuthSettings.RedirectUrl);
var response = await restClient.SendRequest(requestMessage);
var tokenResponse = JsonSerializer.Deserialize<AuthorizationToken>(response);

If I replace the value of appleClientSecret with a JWT that has the exact same properties and was made using the same key, as verified with https://jwt.io, but using nodejs, the rest of the code will work properly. Instead of an invalid_client error I just get the expected output.

Here is the nodejs code I used to generate the token (taken from https://www.npmjs.com/package/apple-auth):

const jwt = require("jsonwebtoken")

let exp = Math.floor(Date.now() / 1000) + ( 86400 * 180 ); // Make it expire within 6 months

const claims = {
    iss: teamId,
    iat: Math.floor(Date.now() / 1000),
    exp,
    aud: 'https://appleid.apple.com',
    sub: clientId,
};

jwt.sign(claims, privateKey, { algorithm: 'ES256', keyid }, (err, token) => {
    console.log(token)
});

I am not sure if anyone else is dealing with this issue or what the cause may be, it has stunlocked me for months. Any help would be appreciated.

DanailStoichkov commented 1 year ago

Hi, @SasLuca I just did a quick testing and everything is working on my end is working as before. Are you sure that your setup in the apple portal is complete? Usually you shouldn't get invalid_client error if there is something wrong with the token, but you will get invalid_grant.

SasLuca commented 1 year ago

Yeah. If I generate the jwt token using nodejs instead of dotnet, but send it with the http client from dotnet, it all works. It seems pretty clear the dotnet token generation is the issue but I can't figure out why.

When I compare the nodejs and dotnet generated tokens on jwt.io there doesn't seem to be any difference.

I am using .net7 with rider on windows 11, maybe I should check the nuget versions of any dependencies too.

One theory I have is that the ecdsa dotnet implementation maybe uses a way to generate the random numbers it needs using the deterministic method which apple might check for and reject, but I'll have to see about that when I debug later today.

If you made a small project to try and reproduce the issue and it works for you, could you share it with me and just remove your private details? It would be interesting to see if that one works.

SasLuca commented 1 year ago

So, I created a fresh repo to reproduce this and it worked fine, so I realized maybe some dependency is different between my main project and this fresh one.

This led me to find the issue, it seems like there is a problem in Microsoft.IdentityModel.JsonWebTokens with version 6.19.0 which makes this library not work. I will investigate further.

SasLuca commented 1 year ago

Ok, so after debugging this with a friend it turns out Microsoft had a regression in Microsoft.IdentityModel.JsonWebTokens version 6.18.0 the details of which you can find here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1861

They started adding this cty field to the header of the JWT that Apple specifically doesn't like. This was then fixed in version 6.20.0. People who encounter this issue can by-pass the issue when using appleauth-net by specifically adding the package with a version >6.20.0 for Microsoft.IdentityModel.JsonWebTokens.

Not sure if the library should address this somehow, might be worth documenting this at the very least in the readme so people can be aware of it, it was very hard for me to find this.

DanailStoichkov commented 1 year ago

@SasLuca Thanks a lot for the effort! We can probably specify versions for the dependencies in the next release.