Sign in with Apple (iOS App + Backend verification) API returns error "invalid_client" Sign in with Apple (iOS App + Backend verification) API returns error "invalid_client" ios ios

Sign in with Apple (iOS App + Backend verification) API returns error "invalid_client"


There are several reasons why it could happen:

  1. client_id for web should be Service id. For apps it should be App bundle id. (Even if you use native apple dialog get code and later pass it to a webserver and then use it to request token API.) sub in JWT call should be the same as client_id.See the answer there Apple forum
  2. Your JWT library does not support encryption required by Apple sign-in. They use the JWT standard for this, using an elliptic curve algorithm with a P-256 curve and SHA256 hash. In other words, they use the ES256 JWT algorithm. Some JWT libraries don’t support elliptic curve methods, so make sure yours does before you start trying this out. ES256 and invalid_client
  3. Dates in token. Try to set the following
 expires: DateTime.UtcNow.AddDays(2), // expiry can be a maximum of 6 months issuedAt: DateTime.UtcNow.AddDays(-1), notBefore: DateTime.UtcNow.AddDays(-1),

It failed with invalid_client on my webserver, since Apple considered it as a certificate from the future, when I had:

 expires: DateTime.UtcNow.AddMinutes(5), // expiry can be a maximum of 6 months issuedAt: DateTime.UtcNow, notBefore: DateTime.UtcNow,
  1. It's also important to specify the User-Agent header when you call the token API. It's also worth mentioning that curl can throw this error, while it will work fine when you call it from a web server.
  2. Make sure you are setting the correct Content-Type: application/x-www-form-urlencoded header instead of Content-Type: application/json that some libraries like axios set by default.


I was scratching my head quite a bit on how to validate the Sign In with Apple from the iOS app on the server side and couldn't find too much documentation about it.

I'll leave my implementation in nodeJS here in case it helps anyone; I actually followed the approach outlined here by Curtis Herbert

  1. From the iOS app you get an ASAuthorizationAppleIDCredential which includes, among other details, a user (id), an email, and an identityToken (short lived JWT)
  2. On the server side you can use Apple Json Web Keys available in https://appleid.apple.com/auth/keys to generate a public key.
    {      "keys": [        {          "kty": "RSA",          "kid": "86D88Kf",          "use": "sig",          "alg": "RS256",          "n": "iGaLqP6y-SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInqUvjJur--hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk-ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ",          "e": "AQAB"        },        {          "kty": "RSA",          "kid": "eXaunmL",          "use": "sig",          "alg": "RS256",          "n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw",          "e": "AQAB"        }      ]    }
  1. Finally, using the public key you can verify the identityToken to make sure it was generated by Apple. I used the library jose for it
    const axios = require('axios').default;    const jose = require('jose')    const {      JWKS,  // JSON Web Key Set (JWKS)      JWT,   // JSON Web Token (JWT)      errors // errors utilized by jose    } = jose        axios.get('https://appleid.apple.com/auth/keys')                .then(function (response) {                    // handle success                    const key = jose.JWKS.asKeyStore(response.data);                    const verified = jose.JWT.verify(identityToken, key);                })                .catch(function (error) {                    // handle error                    console.log(error);                })                .then(function () {                    // always executed                });

At the end you get a an object with the same structure as if you were decoding the JWT (identityToken) in which you can validate that;

    {            "iss": "https://appleid.apple.com",            "aud": "BundleID",            "sub": "credential.user",            "email": "credential.email",        }


Your native APP ID is your bundle ID prefixed with your team ID, seperated by a dot.

"The Apple App ID is a two part string used to identify one or more apps. Specifically, the Apple app ID is your team ID and bundle ID joined with a period, for example: 1A234H7ABC.com.yourdomain.YourApp."

I'm having the same issue getting this to work however.