Post

Replies

Boosts

Views

Activity

Reply to Apple Sign In - invalid_client
Another thing to note. You can only attempt to validate the AuthorizationCode once. Attempting to send it twice will result in the "invalid_grant" error. Also, the way I discovered that c# was broken and nothing else was that I attempted to execute the call using curl. curl -i POST "https://appleid.apple.com/auth/token" \ -H "content-type: application/x-www-form-urlencoded" \ -d "client_id=com.appname.appname" \ -d "client_secret=JWT_GENERATED_IN_C#" \ -d "code=AUTHORIZATION_CODE_FROM_APP" \ -d "grant_type=authorization_code" \ -d "redirect_uri=https://myredirecturi.com"
Aug ’21
Reply to Apple Sign In - invalid_client
So, I finally solved this. Hopefully it helps someone in the future. I think the issue was my lack of understanding how to properly set the header in an HttpClient. My new method to validate the Token is public async Task<AppleVerifySignInTokenResponse> ValidateSignInToken(...) { try { using (var httpClient = new HttpClient()) { httpClient.BaseAddress = "https://appleid.apple.com/auth/token"; var dictionary = new Dictionary<string, string> { { "client_id", "com.appname.appname" }, { "client_secret", GenerateAppleClientSecret() }, { "code", authorizationCode }, { "grant_type", "authorization_code" }, { "redirect_uri", "https://myredirecturi.com" }, }); using (var content = new FormUrlEncodedContent(dictionary)) { content.Headers.Clear(); content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); var response = await httpClient.PostAsync("", content).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; } } Also, note that I was struggling to figure out what my client_id should be. I have my app (com.appname.appname) and my service (com.appname.appnameserice). I did not use the service one anywhere. This is what I use to generate the client_secret public string GenerateAppleClientSecret() { // Content of the .p8 file (without -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----). string privateKey = "MIG..."; // The 10-character key identifier from the portal (Also the name of the .p8 file. If file is AuthKey_12345.p8, put 12345) string keyId = _KeyId; // The Bundle Id of the iOS app (found in app store connect -> app -> App Information) string clientId = "com.appname.appname"; // Found in developer portal string teamId = "SLT12345"; var now = DateTimeOffset.UtcNow; //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 tokenHandler = new JwtSecurityTokenHandler(); var tokenDescriptor = new SecurityTokenDescriptor { Issuer = teamId, Subject = new ClaimsIdentity(new Claim[] { new Claim("iss", teamId), new Claim("iat", now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim("exp", now.AddDays(7).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new Claim("aud", "https://appleid.apple.com"), new Claim("sub", clientId) }), Expires = DateTime.UtcNow.AddMinutes(5), SigningCredentials = signingCredentials, }; var token = tokenHandler.CreateJwtSecurityToken(tokenDescriptor); if (commandModel.Kid.IsNullOrWhiteSpace() == false) { token.Header.Add("kid", keyId); } return tokenHandler.WriteToken(token); }
Aug ’21