- read

Golang — Handling Appstore Server-to-Server V2 Notifications

Sumita K 100

Photo by Canopas

App Store has released server-to-server notification version 2 with more events(notification types and sub types) than version 1.

In version2, they are sending JSON Web Signature (JWS) formatted (Which is a JWT token) notification to improve the security of data.

In this article, we will show how to decode SNS notifications v2 in a go application.

Background

When I started implementing app store SNS v2 in golang, I was a bit confused about how to verify and parse JWS data.

Thanks to this thread, I learned lots of important things there. While doing the implementation, I simplified it to make understanding easier.

This article will help you learn about SNS implementation in a very simple way.

Before beginning with the actual implementation, let’s get familiar with the request body of the notification we get from apple first!

Notification V2 request body

{ 
"signedPayload":"eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTURDQ0E3YWd
Bd0lCQWdJUWFQb1BsZHZwU29FSDBsQnJqRFB2OWpBS0JnZ3Foa2pPUFF..."
}

It’s signed by the App Store and contains all required data including transaction info and renewal info of app store purchases.

FYI : You can parse and review signedPayload on jwt.io

Whenever we receive a notification from the app store, we need to first verify the request and then we need to parse that token to get payload data.

JWS contains 3 parts in the token separated by .

  1. Header (contains two fields : Algorithm and Token type)
  2. Payload (contains data)
  3. Signature

For verifying the request we need to parse the header part. If it's verified successfully, then we will parse the payload otherwise decline it.

We have divided the decoding process of signedPayload in the following steps —

  1. Extract header from JWS token
  2. Verify header with an app store key
  3. Extract the public key from the token to parse payload data
  4. Prepare structures to bind notification
  5. Parse payload and bind it to structures

Let’s learn it step by step.

1. Extract header from JWS token

First, we will define the structure to get a header with two fields algorithm and token type.

  • Algorithm: ES256
  • Token type: x5c (X.509 certificate chain) contains the X.509 public key certificate or certificate chain [RFC5280] corresponding to the key used to digitally sign the JWS.

Let’s understand it,

  • strings.Split: split string by . and extract the first part of the split array which is a header.
  • base64.RawStdEncoding.DecodeString(tokenArr[0]) decodes base64 header string to a byte with raw encoding.
  • Unmarshal bytes to go structure which is NotificationHeader . We need to use X5c of structure to get the certificate.
  • X5c is an array of 3 elements which is a certificate chain.
    X5c[0] : use for extracting public key
    X5c[1] : intermediate certificate used to verify the header
    X5c[2] : root certificate used to verify the header

2. Verify header with an app store key

In this step, we will verify the X5c certificates using the app store key, which we can download from Apple Root CA — G3 Root.

Convert it to PEM using the following command.

openssl x509 -in AppleRootCA-G3.cer -out cert.pem

Verify the X5c certificate using the app store key.

  • x509.NewCertPool() will create a new and empty certificate pool, and we will append the app store cert to this pool.
  • APP_STORE_NOTIFICATION_ROOT_CERT is converted PEM key from the app store certificate. Copy the PEM key from the file and assign it here.
  • roots.AppendCertsFromPEM parse and append the PEM key to the cert pool.
  • We also parse and append intermediate certificates to the certificate pool.
  • x509.ParseCertificate(certByte) parses extracted X5c certificate.
  • x509.VerifyOptions prepare x509 verify options.
  • cert.Verify(opts) verifies X5c certificate using verifyOptions.

3. Extract the public key from the token to parse payload data

Now we have verified the notification request, it's time to parse payload data. For that, we need a public key. Let’s get that from the header.

We have reused code from step 1 and step 2 and extract the public key from the header.

  • Get the certificate from extractHeaderByIndex method.
  • Parse certificate string to bytes.
  • Extract ecdsa.PublicKey from certificate bytes.

That’s it. You will have the public key now.

4. Prepare structures to bind notification

When we parsed the JWS token on jwt.io, we see that payload has some fields. We will define go structures for required fields from those fields.

From the payload, I have identified 3 basic structures that we will require to change the user’s subscription status.

  1. NotificationPayload
  2. TransactionInfo
  3. RenewalInfo

SNS v2 has included notification types along with subtypes to get detailed information about subscription status.

You can understand all the fields on the app store’s official documentation.

You have noticed jwt.StandardClaims in all the structures as we need them to parse JWS data.

Let’s go to the final step.

5. Parse payload and bind it with structures

Here, We have used the public key method from step 3 to parse the JWS token string.

  • In notificationPayload, we have parsed the main signedPayload string. It has a data field, which contains transactionInfo and renewalInfo in JWS format string.
  • In TransactionInfo, we have parsed data.transactionInfo string.
  • In renewalInfo, we have parsed data.renewalInfo string.

Final method

The above steps are used to decode the notification payload signed by the app store.

But we have to do that in order as we have discussed earlier, if the certificate is verified, then only we will parse data otherwise declined it.

Now we have payload, transactionInfo and renewalInfo, Using these data we can update the user’s subscription status based on the notificationType and it’s subType .

Conclusion

That’s it for today. Hope you have an understanding of how we can decode SNS v2 notification data and use it at the server. Similar way, you can implement SNS v2 in any backend language like ruby, PHP, or python.

You can refer to app store documentation to decide what user’s data you have to update at your backend from notification.

Feedback and suggestions are always welcome. If you have any, feel free to add them in the comments section.

Thanks for your support!

If you like what you read, be sure to 👏 👏👏 it below — as a writer it means the world!

Follow Canopas or connect with us on Twitter to get updates on interesting articles!