Hello,
I am working on apple sign in verification process. Currently, I have a React Native iOS app that uses the @invertase/react-native-apple-authentication package to handle verification on the frontend. This seems to be working just fine.
My iOS app Bundle Id is "com.appname.appname"
When the user signs in, I get an IdentityToken and a AuthorizationCode. I pass both of these values to the backend.
The backend is a .NET Core API. In the Apple Developer Portal, I created 1 key with the enabled service, "Sign In with Apple" and the Primary App Id points to my 1 app. Under Grouped App IDs, it points to my service, "com.appname.appnameservice".
I downloaded the .p8 file from here and saved it for later. My KeyId is "T5LGCK354D".
Then, in Identifiers, I created 2. 1 App ID with the identifier, "com.appname.appname". That also has "Sign In with Apple" and is linked to my primary app id.
The other Identifier is a ServiceId and its Identifier name is "com.appname.appnameservice". It too has "Sign In with Apple" configured, pointed to my Primary app (com.appname.appname) and has 2 domains configured, and 1 return url configured.
It is worth noting, that I also configured Sign in with Apple for Email Communications, and my domain has a green check with SPF next to it.
Finally, in the backend, I have tried a bunch of things. Currently, my api has the following to generate the client secret:
public string GenerateAppleClientSecret()
{
string privateKey = "MIGTAgEAMBMGByqGSM49..............5rn4GrzFepyloJrr6ECn.....gYIKoZIzj0DAQehR......UZOi88Qdb8ZTU9zM4/jzt0pHZ9uU2HyAbK2//UA6.....mGqkKDqybf";
string keyId = "T5LGCK354D"; //The 10-character key identifier from the portal.
string clientId = "com.appname.appnameservice";
string teamId = "SLT8SJ897V";
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
var cngKey = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
var now = DateTime.UtcNow;
var handler = new JwtSecurityTokenHandler();
var token = handler.CreateJwtSecurityToken(
issuer: teamId,
audience: "https://appleid.apple.com",
subject: new ClaimsIdentity(new List<Claim> {new Claim("sub", clientId)}),
expires: DateTime.UtcNow.AddMinutes(5), // expiry can be a maximum of 6 months
issuedAt: DateTime.UtcNow,
notBefore: DateTime.UtcNow,
signingCredentials: new SigningCredentials(new ECDsaSecurityKey(new ECDsaCng(cngKey)), SecurityAlgorithms.EcdsaSha256)
);
token.Header.Add("kid", keyId);
return handler.WriteToken(token);
}
I have tried this too
public string GenerateAppleClientSecret()
{
string privateKey = "MIGTAgEAMBMGByqGSM49..............5rn4GrzFepyloJrr6ECn.....gYIKoZIzj0DAQehR......UZOi88Qdb8ZTU9zM4/jzt0pHZ9uU2HyAbK2//UA6.....mGqkKDqybf";
string keyId = "T5LGCK354D";
string clientId = "com.appname.appnameservice";
string teamId = "SLT8SJ897V";
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
//Import the key using a Pkcs8PrivateBlob.
var cngKey = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
//Create new ECDsaCng object with the imported key.
var ecDsaCng = new ECDsaCng(cngKey);
ecDsaCng.HashAlgorithm = CngAlgorithm.ECDsaP256;
//Create new SigningCredentials instance which will be used for signing the token.
var signingCredentials = new SigningCredentials(new ECDsaSecurityKey(ecDsaCng), SecurityAlgorithms.EcdsaSha256);
var now = DateTime.UtcNow;
//Create new list with the required claims.
var claims = new List<Claim>
{
new Claim("iss", teamId),
new Claim("iat", EpochTime.GetIntDate(now).ToString(), ClaimValueTypes.Integer64),
new Claim("exp", EpochTime.GetIntDate(now.AddMinutes(5)).ToString(), ClaimValueTypes.Integer64),
new Claim("aud", "https://appleid.apple.com"),
new Claim("sub", clientId)
};
//Create the JSON Web Token object.
var token = new JwtSecurityToken(
issuer: teamId,
claims: claims,
expires: now.AddMinutes(5),
signingCredentials: signingCredentials);
token.Header.Add("kid", keyId);
//Return the JSON Web Token as a string.
return tokenHandler.WriteToken(token);
}
Then to validate the token I have this
public async Task<AppleVerifySignInTokenResponse> ValidateSignInToken(...)
{
try
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = "https://appleid.apple.com/auth/token";
var jsonItem = JsonConvert.SerializeObject(new
{
client_id = "com.appname.appnameservice",
client_secret = GenerateAppleClientSecret(),
code = authorizationCode, // AuthorizationCode from frontend
grant_type = "authorization_code",
redirect_uri = "https://myredirecturi.com" // identical to the one in developer portal
});
var httpContent = new StringContent(jsonItem, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await httpClient.PostAsync("", httpContent).ConfigureAwait(false);
if (response.IsSuccessStatusCode == true && response.Content != null)
{
var json = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<AppleVerifySignInTokenResponse>(json);
}
return null;
}
}
catch (Exception ex)
{
return null;
}
}
The response from above keeps returning "invalid_client" no matter what I do... I have tried changing the clientId from "com.appname.appnameservice" to "com.appname.appname", in some of the places and all of the places. I have tried generating a new .p8 file and using that.
Any Ideas? I have spent probably a week on this :'( Thanks!
Note: The private key has a bunch of periods in it because I wanted to redact most of the content. I will generate a new one once I get this working.
Also, this is kind of insane. If apple is going to require us to support apple sign in, they need better documentation and error messages. There seems to be so many developers lost on what to do with very little success.
Topic:
App & System Services
SubTopic:
Core OS
Tags:
iOS
Sign in with Apple
Sign in with Apple REST API