How to implement push notifications based on tokens on tokens (using the p8 file) in C #?

For an application with some chat-based features, I want to add support for push notifications to receive new messages. I want to use Apple's new token-based authentication (.p8 file), but I cannot find much information about the server side.

I came across the following message: How to use APN Auth Key (.p8 file) in C #?

However, the answer was not satisfactory since there were no details on how:

  • establish connection with APN
  • use the p8 file (with the exception of some encoding)
  • send data to Apple Push notification service
+10
source share
4 answers

You cannot currently do this on the raw .NET Framework. The new JWT-based APNS server uses only the HTTP / 2 protocol, which the .NET Framework does not yet support.

The .NET Core version of System.Net.Http , however, does if you meet the following prerequisites:

  • On Windows, you must run Windows 10 Anniversary Edition (v1607) or higher or an equivalent build of Windows Server 2016 (I think).
  • On Linux, you must have a libcurl version that supports HTTP / 2.
  • On macOS, you need to compile libcurl with HTTP / 2 support, and then use the DYLD_INSERT_LIBRARIES environment variable to load your custom libcurl assembly.

You should be able to use the .NET Core System.Net.Http version in the .NET Framework if you really want to.

I have no idea what is going on in Mono, Xamarin or UWP.

There are three things you need to do:

  • Disassemble the secret key that is provided to you. This is currently the ECDSA key, and you can load it into the System.Security.Cryptography.ECDsa object.
    • On Windows, you can use the CNG API. After parsing the DER part based on the base64 key file, you can create the key using new ECDsaCng(CngKey.Import(data, CngKeyBlobFormat.Pkcs8PrivateBlob)) .
    • MacOS or Linux does not have a supported API, and you need to analyze the DER structure yourself or use a third-party library.
  • Create a Token / Bearer Token JSON Token. If you use the System.IdentityModel.Tokens.Jwt package from NuGet, this is pretty simple. You will need a key identifier and a team identifier from Apple.
 public static string CreateToken(ECDsa key, string keyID, string teamID) { var securityKey = new ECDsaSecurityKey(key) { KeyId = keyID }; var credentials = new SigningCredentials(securityKey, "ES256"); var descriptor = new SecurityTokenDescriptor { IssuedAt = DateTime.Now, Issuer = teamID, SigningCredentials = credentials }; var handler = new JwtSecurityTokenHandler(); var encodedToken = handler.CreateEncodedJwt(descriptor); return encodedToken; } 
  1. Send an HTTP / 2 request. This is fine, but you need to do two more things:

    • Set yourRequestMessage.Version to new Version(2, 0) to execute the request using HTTP / 2.
    • Set yourRequestMessage.Headers.Authorization to new AuthenticationHeaderValue("bearer", token) to provide a bearer authentication token / JWT with your request.

    Then just put your JSON in the HTTP request and send it to the correct URL.

+10
source

He tried the above on ASP.NET CORE 2.1 and 2.2 to no avail. I always got the answer β€œThe received message was unexpected or poorly formatted” with HttpVersion20 turned on, which made me doubt whether the implementation of http2 is specific.

The following describes what worked on ASP.NET CORE 3.0;

  var teamId = "YOURTEAMID"; var keyId = "YOURKEYID"; try { // var data = await System.IO.File.ReadAllTextAsync(Path.Combine(_environment.ContentRootPath, "apns/"+config.P8FileName)); var list = data.Split('\n').ToList(); var prk = list.Where((s, i) => i != 0 && i != list.Count - 1).Aggregate((agg, s) => agg + s); // var key = new ECDsaCng(CngKey.Import(Convert.FromBase64String(prk), CngKeyBlobFormat.Pkcs8PrivateBlob)); // var token = CreateToken(key, keyId, teamId); // var deviceToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXX"; var url = string.Format("https://api.sandbox.push.apple.com/3/device/{0}", deviceToken); var request = new HttpRequestMessage(HttpMethod.Post, url); // request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); // request.Headers.TryAddWithoutValidation("apns-push-type", "alert"); // or background request.Headers.TryAddWithoutValidation("apns-id", Guid.NewGuid().ToString("D")); //Expiry // request.Headers.TryAddWithoutValidation("apns-expiration", Convert.ToString(0)); //Send imediately request.Headers.TryAddWithoutValidation("apns-priority", Convert.ToString(10)); //App Bundle request.Headers.TryAddWithoutValidation("apns-topic", "com.xx.yy"); //Category request.Headers.TryAddWithoutValidation("apns-collapse-id", "test"); // var body = JsonConvert.SerializeObject(new { aps = new { alert = new { title = "Test", body = "Sample Test APNS", time = DateTime.Now.ToString() }, badge = 1, sound = "default" }, acme2 = new string[] { "bang", "whiz" } }) // request.Version = HttpVersion.Version20; // using (var stringContent = new StringContent(body, Encoding.UTF8, "application/json")) { //Set Body request.Content = stringContent; _logger.LogInformation(request.ToString()); // var handler = new HttpClientHandler(); // handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; // handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true; //Continue using (HttpClient client = new HttpClient(handler)) { // HttpResponseMessage resp = await client.SendAsync(request).ContinueWith(responseTask => { return responseTask.Result; // }); // _logger.LogInformation(resp.ToString()); // if (resp != null) { string apnsResponseString = await resp.Content.ReadAsStringAsync(); // handler.Dispose(); //ALL GOOD .... return; } // handler.Dispose(); } } } catch (HttpRequestException e) { _logger.LogError(5, e.StackTrace, e); } 

For CreateToken () See above recommended solution from yaakov,

+1
source

You can use PushSharp , which is a nuget package that supports push notification for Apple, as well as for Google and Microsoft.

Here are links to github and nuget .

This is a sample for sending push notifications for Apple:

 // Configuration (NOTE: .pfx can also be used here) var config = new ApnsConfiguration (ApnsConfiguration.ApnsServerEnvironment.Sandbox, "push-cert.p12", "push-cert-pwd"); // Create a new broker var apnsBroker = new ApnsServiceBroker (config); // Wire up events apnsBroker.OnNotificationFailed += (notification, aggregateEx) => { aggregateEx.Handle (ex => { // See what kind of exception it was to further diagnose if (ex is ApnsNotificationException) { var notificationException = (ApnsNotificationException)ex; // Deal with the failed notification var apnsNotification = notificationException.Notification; var statusCode = notificationException.ErrorStatusCode; Console.WriteLine ($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}"); } else { // Inner exception might hold more useful information like an ApnsConnectionException Console.WriteLine ($"Apple Notification Failed for some unknown reason : {ex.InnerException}"); } // Mark it as handled return true; }); }; apnsBroker.OnNotificationSucceeded += (notification) => { Console.WriteLine ("Apple Notification Sent!"); }; // Start the broker apnsBroker.Start (); foreach (var deviceToken in MY_DEVICE_TOKENS) { // Queue a notification to send apnsBroker.QueueNotification (new ApnsNotification { DeviceToken = deviceToken, Payload = JObject.Parse ("{\"aps\":{\"badge\":7}}") }); } // Stop the broker, wait for it to finish // This isn't done after every message, but after you're // done with the broker apnsBroker.Stop (); 
0
source
 private string GetToken() { var dsa = GetECDsa(); return CreateJwt(dsa, "keyId", "teamId"); } private ECDsa GetECDsa() { using (TextReader reader = System.IO.File.OpenText("AuthKey_xxxxxxx.p8")) { var ecPrivateKeyParameters = (ECPrivateKeyParameters)new Org.BouncyCastle.OpenSsl.PemReader(reader).ReadObject(); var q = ecPrivateKeyParameters.Parameters.G.Multiply(ecPrivateKeyParameters.D).Normalize(); var qx = q.AffineXCoord.GetEncoded(); var qy = q.AffineYCoord.GetEncoded(); var d = ecPrivateKeyParameters.D.ToByteArrayUnsigned(); // Convert the BouncyCastle key to a Native Key. var msEcp = new ECParameters {Curve = ECCurve.NamedCurves.nistP256, Q = {X = qx, Y = qy}, D = d}; return ECDsa.Create(msEcp); } } private string CreateJwt(ECDsa key, string keyId, string teamId) { var securityKey = new ECDsaSecurityKey(key) { KeyId = keyId }; var credentials = new SigningCredentials(securityKey, "ES256"); var descriptor = new SecurityTokenDescriptor { IssuedAt = DateTime.Now, Issuer = teamId, SigningCredentials = credentials, }; var handler = new JwtSecurityTokenHandler(); var encodedToken = handler.CreateEncodedJwt(descriptor); return encodedToken; } 
0
source

All Articles