How to generate JWT token for the IOS devices
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 hours ago
Hi Guys,
ServiceNow supports RS256 for JWT signing, but Apple APNs requires ES256 using a .p8 private key.
Is there any way to generate an ES256 JWT in ServiceNow using the Apple .p8 key (or after converting it to JKS), without using a MID Server or external service?
Any help would be appreciated. Thanks!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
3 hours ago - last edited 3 hours ago
ServiceNow's native JWT framework (sn_auth.GlideJWTAPI and the JWT Provider infrastructure) is hardcoded and is simply not exposed as a signing algorithm in the platform's JWT utilities as of recent releases.
The .p8 file Apple provides is a raw PKCS#8 EC private key — and even if you converted it to JKS, the JKS would contain an EC key entry that ServiceNow's JWT signer would still refuse to use because the algorithm gap is the blocker, not the key format.
Manual JWT Construction via GlideTextEncoder + Java crypto (Best viable option)
ServiceNow Rhino/server-side JS has access to underlying Java classes. You can construct the ES256 JWT manually by reaching into java.security:
function generateApnsJWT(teamId, keyId, privateKeyPem) {
// 1. Build header and payload
var header = {
alg: "ES256",
kid: keyId
};
var payload = {
iss: teamId,
iat: Math.floor(new Date().getTime() / 1000)
};
// 2. Base64URL encode header and payload
function base64UrlEncode(str) {
var encoded = GlideStringUtil.base64Encode(str);
return encoded.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
var headerEncoded = base64UrlEncode(JSON.stringify(header));
var payloadEncoded = base64UrlEncode(JSON.stringify(payload));
var signingInput = headerEncoded + '.' + payloadEncoded;
// 3. Load the EC private key via Java
var Base64 = java.util.Base64;
var KeyFactory = java.security.KeyFactory;
var Signature = java.security.Signature;
var PKCS8EncodedKeySpec = java.security.spec.PKCS8EncodedKeySpec;
// Strip PEM headers from .p8 content
var pemClean = privateKeyPem
.replace('-----BEGIN PRIVATE KEY-----', '')
.replace('-----END PRIVATE KEY-----', '')
.replace(/\s+/g, '');
var keyBytes = Base64.getDecoder().decode(pemClean);
var keySpec = new PKCS8EncodedKeySpec(keyBytes);
var keyFactory = KeyFactory.getInstance('EC');
var privateKey = keyFactory.generatePrivate(keySpec);
// 4. Sign with SHA256withECDSA
var signer = Signature.getInstance('SHA256withECDSA');
signer.initSign(privateKey);
signer.update(new java.lang.String(signingInput).getBytes('UTF-8'));
var derSignature = signer.sign();
// 5. Convert DER-encoded signature to raw R||S format (required for JWT ES256)
var rawSignature = derToJoseSignature(derSignature);
var sigEncoded = Base64.getUrlEncoder().withoutPadding().encodeToString(rawSignature);
return signingInput + '.' + sigEncoded;
}
// DER → JOSE (R||S) conversion — critical step Apple requires
function derToJoseSignature(derBytes) {
// DER structure: 0x30 [total-len] 0x02 [r-len] [r] 0x02 [s-len] [s]
var der = Java.from(derBytes); // convert to JS array
var offset = 3; // skip 0x30, length, 0x02
var rLen = der[offset++];
var r = der.slice(offset, offset + rLen);
offset += rLen + 1; // skip 0x02
var sLen = der[offset++];
var s = der.slice(offset, offset + sLen);
// Pad or trim to exactly 32 bytes each
function to32Bytes(arr) {
while (arr.length > 32) arr = arr.slice(1); // trim leading zero padding
while (arr.length < 32) arr.unshift(0); // left-pad if short
return arr;
}
var result = to32Bytes(r).concat(to32Bytes(s));
return Java.to(result, 'byte[]');
}
⚠️ The DER → JOSE conversion step is the most commonly missed part. Java's
SHA256withECDSAoutputs DER-encoded signatures, but JWT ES256 requires the raw concatenated R||S format. Skipping this will produce a JWT that looks valid but Apple will always reject.
