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.
Post
Replies
Boosts
Views
Activity
Hello,
My app has subscriptions a user can purchase. There are n tiers, and when they pick one and pay, the app exposes additional functionality. These subscriptions are all handled within the app itself. I use Stripe to handle payments.
Well, recently I submitted my app for review, and it got rejected. The reason being that since I offer subscriptions in the app, I have to also offer them through the app store.
I have gone through the entire process of setting this up to support in app subscription purchases. But one thing I am confused about is how to determine the user I am supposed to provision features to.
The app store server notification doesn't provide any user details. The verify receipt api does not either.
Basically, all apple tells me is someone purchased this subscription. As far as I can tell, they don't say who. How do I handle this?
Another issue is, what if the user has not created an account with us yet but they try to purchase the subscription through the app store. How do I handle this?
Or, what if they did create an account, but it's not associated to their apple account?
A bit more context. Our users can either create an account and log in directly through our service, through google, or through apple.
Thanks!