SSI Token Library
The SSI token library is a Java library containing utility classes to generate and validate link and SSI tokens. You can use this library to perform the following actions.
- Generate link tokens to issue to Amazon during account linking setup.
- Validate and decode SSI tokens during new sign-in requests to your app.
- Validate and decode link tokens during new sign-in requests to your app; a link token is extracted by decoding an SSI token.
- Generate SSI tokens to mimic the behavior of the Amazon SSI server in your internal tests.
To use the SSI token library in your project, add the JAR file, SimpleSignInTokenCommon-<x.y>.jar. You can download the JAR here, or get it from the libs folder of the SSI sample app, included in the Appstore SDK. This JAR file will be a dependency in your project. For other direct and transitive Maven dependencies, see the build.gradle file in the SSI sample app.
The following sections explain the library usage for the preceding actions, with sample code references. For a more detailed description of individual classes and methods in Javadoc format, see the full SSI Token Library API reference.
- Generate link tokens
- Validate and decode SSI token
- Validate and decode the link token
- Generate an SSI token
Generate link tokens
The LinkTokenV1Provider
class provides the APIs to generate link tokens. The constructor used to instantiate LinkTokenV1Provider
takes two input parameters - instances of your implementations of the ILinkTokenCryptoKeyProvider
and IAppStorePublicKeyProvider
interfaces. These implementations include the logic to retrieve cryptographic keys from your key store.
The logic to retrieve cryptographic keys and the public key pair is inside the ILinkTokenCryptoKeyProvider
and IAppStorePublicKeyProvider
interfaces. Use the following code to create a LinkTokenV1Provider
object, which is used to generate link tokens.
LinkTokenV1Provider linkTokenV1Provider =
new LinkTokenV1Provider(
new LinkTokenCryptoKeyProvider(), new AppStorePublicKeyProvider());
Generate and cache your key
The following sample implementation of ILinkTokenCryptoKeyProvider
generates and caches an AES 128-bit key during instantiation of the class and provides this key any time it's requested. In a real implementation, you must add logic to fetch the keys from a secure store where your app keys are pre-created and stored.
static class LinkTokenCryptoKeyProvider implements ILinkTokenCryptoKeyProvider {
SimpleSignInCryptoKey < SecretKey > cryptoKey;
LinkTokenCryptoKeyProvider() throws TokenException {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, new SecureRandom());
SecretKey secretKey = keyGenerator.generateKey();
cryptoKey = new SimpleSignInCryptoKey(secretKey, "key.1");
} catch (NoSuchAlgorithmException e) {
throw new TokenException(e);
}
}
@Override
public SimpleSignInCryptoKey < SecretKey > getEncryptionKey(IRequestContext iRequestContext) throws TokenException {
/*
Return the encryption key to use for a given application revision. Developers who own
multiple applications with each application having multiple revisions, can use
IRequestContext object to pass relevant context about the request origin and
pick the appropriate key to use.
*/
return cryptoKey;
}
@Override
public SimpleSignInCryptoKey < SecretKey > getDecryptionKey(IRequestContext iRequestContext, String s) throws TokenException {
/*
Return the decryption key to use for a given application revision. Developers who own
multiple applications with each application having multiple revisions, can use
IRequestContext object to pass relevant context about the request origin and
pick the appropriate key to use.
*/
return cryptoKey;
}
}
Generate static key pairs
The following two code samples used to implement IAppStorePublicKeyProvider
, generate a static key pair locally and expose the public key component of the generated key pair through its getPublicKey()
method. In the actual implementation, you must add logic to fetch key material from a secure store where the public key assigned by Appstore is stored.
private static KeyPair appStoreKeyPair;
static {
KeyPairGenerator keyPairGenerator;
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
keyPairGenerator.initialize(2048);
appStoreKeyPair = keyPairGenerator.generateKeyPair();
}
static class AppStorePublicKeyProvider implements IAppStorePublicKeyProvider {
@Override
public SimpleSignInCryptoKey < PublicKey > getPublicKey(IRequestContext iRequestContext)
throws AppStorePublicKeyException {
/*
The KeyIdentifier "key.1" is hard-coded here. In the actual implementation, developers would have
added relevant details about the request origin (i.e., application and its version) into
the IRequestContext object. These details will be used to determine the key identifier
and hence the appropriate public key to use.
*/
SimpleSignInCryptoKey < PublicKey > appStorePublicKey =
new SimpleSignInCryptoKey < > (appStoreKeyPair.getPublic(), "key.1");
return appStorePublicKey;
}
}
Generate a link token
Now, you can generate a link token by invoking the generateLinkToken()
method. The request object of type GenerateLinkTokenV1Request
contains the details to be encoded inside the token.
GenerateLinkTokenV1Request request = GenerateLinkTokenV1Request.builder()
.directedAmazonUserId("amazonUserId")
.partnerUserId("partnerUserId")
.customFields(null) //Map of custom fields
.requestContext(requestContext)
.build();
LinkTokenContainer linkTokenContainer = linkTokenV1Provider.generateLinkToken(request);
The LinkTokenContainer
object returned as an output contains the following data.
- token: The link token payload. This is the plain string that captures the identity of a user within your system. It's used as proof of linkage with an Amazon user.
- tokenSchema: The link token schema,
LINK-TOKEN-1.0
. - linkSigningKeyEncrypted: The link signing key. An EC private key for which the SSI tokens for this link should be signed. Encrypted using your Appstore public key.
Validate and decode SSI token
To validate and decode the SSI token, use the decodeAndVerifyToken()
method of the SSITokenV1Validator
class. This method verifies the signature of the SSI token and ensures the token isn't expired.
SSITokenV1Validator ssiTokenV1Validator
= new SSITokenV1Validator(linkTokenV1Provider);
SSITokenInfo ssiTokenInfo = ssiTokenV1Validator
.decodeAndVerifyToken(ssiToken, requestContext);
The input parameters used in this example are:
linkTokenV1Provider
- ALinkTokenV1Provider
object passed into the constructor ofSSITokenV1Validator
. This is the same instance ofLinkTokenV1Provider
that you set up for link token generation.requestContext
- An instance of your implementation of theIRequestContext
interface. This object contains the details needed for your implementation ofILinkTokenCryptoKeyProvider
to get to the appropriate decryption key, to decrypt the link token, and to extract a link verification key. A link verification key is used to verify the SSI token signature embedded inside it.ssiToken
- TheSSIToken
object that you want to decode and verify.
SSI tokens
SSI tokens have the format SSIToken(token=<xxxxx.yyyyy.zzzzz>, schema=SSI-TOKEN-1.0)
. The token
here is a JSON Web Token (JWT), which typically looks like "xxxxx.yyyyy.zzzzz". Where xxxxx is the Base64Url encoded header, yyyyy is the Base64Url encoded payload, and zzzzz is the computed signature of xxxxx and yyyyy, with an app specific private key.
A decoded SSI token has the following format:
SSITokenInfo(linkInfo=SSITokenInfo.LinkInfo(directedAmazonUserId=amazonUserId,
directedPartnerUserId=partnerUserId,
linkToken=LinkToken(token=<A plain string link token that captures the identity
of a user within your system and proves linkage with an Amazon user. This token
string was passed during the account linking request>,tokenSchema=LINK-TOKEN-1.0)),
tokenMetadata=SSITokenV1Metadata(super=SSITokenMetadata(issuedAt=1628082286000,
notValidBefore=1628082286000, expiresAt=1628082586000),issuer=http://ssi.amazon.com,
audience=TEST_DEVELOPER_ID,jwtId=66141c38-1bb5-4336-a2a6-55247345dc99))
decodeToken()
method exposed by the SSI token validator.Validate and decode the link token
Once an SSI token is successfully validated, validate the wrapped link token and extract customer identification details from the token. Use the validateLinkToken()
method of LinkTokenV1Provider
to achieve this.
LinkTokenInfo linkTokenInfoFromSSIToken = linkTokenV1Provider
.validateLinkToken(ssiTokenInfo, requestContext);
The input parameters to the validateLinkToken()
method used in this example are:
ssiTokenInfo
- An instance of theSSITokenInfo
type, which contains the details decoded from an SSI Token.requestContext
- An instance of your implementation of theIRequestContext
interface. This object contains the details needed in your implementation ofILinkTokenCryptoKeyProvider
to get the decryption key used to decrypt the link token.
Generate an SSI token
You don't need to generate SSI tokens yourself for production apps, but you might want to generate tokens for testing purposes. You can generate SSI tokens with the SSI token library, following these steps:
- Generate a link token by following the steps described in Generate a link token.
-
Assume for your test app you have access to both private and public keys of the Appstore key pair. Although, for a production app, you'll have access to the public key only. Implement the
IAppStorePrivateKeyProvider
interface to enable access to the private key to decrypt the link signing key, which is issued along with the link token and encrypted using the Appstore public key.The following sample implementation of the
IAppStorePrivateKeyProvider
interface generates a static key pair locally and exposes the private key through itsgetPrivateKey()
method. Use both code samples.private static KeyPair appStoreKeyPair; static { KeyPairGenerator keyPairGenerator; try { keyPairGenerator = KeyPairGenerator.getInstance("RSA"); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } keyPairGenerator.initialize(2048); appStoreKeyPair = keyPairGenerator.generateKeyPair(); }
static class AppStorePrivateKeyProvider implements IAppStorePrivateKeyProvider { @Override public SimpleSignInCryptoKey < PrivateKey > getPrivateKey(String app, String appVersion) throws AppStorePrivateKeyException { SimpleSignInCryptoKey < PrivateKey > appStorePrivateKey = new SimpleSignInCryptoKey < > (appStoreKeyPair.getPrivate(), "key.1"); return appStorePrivateKey; } }
-
Decrypt the link signing key using a
LinkSigningKeyDecryptor
object. TheLinkSigningKeyDecryptor
instance is built using your implementation of theIAppStorePrivateKeyProvider
interface.LinkSigningKeyDecryptor linkSigningKeyDecryptor = new LinkSigningKeyDecryptor(new AppStorePrivateKeyProvider()); String linkSigningKey = linkSigningKeyDecryptor .decryptLinkSigningKey(linkTokenContainer.getLinkSigningKeyEncrypted(), "TEST_ASIN", "TEST_APP_VERSION");
-
Generate an Amazon representation of the account linking relationship by building a
Link
object as follows.Link link = Link.builder() .linkId("amzn1.ssi.link.123456") .directedAmazonUserId("amazonUserId") .directedPartnerUserId("partnerUserId") .identityProvider("partnerIdp") .linkedTime(System.currentTimeMillis()) .linkSigningKey(linkSigningKey) .linkToken(linkTokenContainer.getLinkToken()) .build();
- Generate an SSI token for the linked account represented using the
Link
object created in the previous step. Use thegenerateToken()
API exposed through theSSITokenV1Generator
class to create an SSI token as follows.
SSITokenV1Generator ssiTokenV1Generator = new SSITokenV1Generator();
ProductInfo productInfo = ProductInfo.builder()
.vendorId("TEST_DEVELOPER_ID")
.asin("TEST_ASIN")
.productVersion("TEST_APP_VERSION")
.build();
SSIToken ssiToken = ssiTokenV1Generator.generateToken(link, productInfo);
Last updated: Mar 13, 2023