Authenticate Your Identity
In this how-to, you will learn to authenticate your identity. Make sure to read the general authentication concept, so you can fully understand the Authentication Workflow.
This example uses the following identity:
{
identityId: 'did:iota:8BAmUqAg4aUjV3T9WUhPpDnFVbJSk16oLyFq3m3e62MF',
secretKey: '5N3SxG4UzVDpNe4LyDoZyb6bSgE9tk3pE2XP5znXo5bF'
}
The Integration Services Java library is still in BETA. Please note that not everything may yet run smoothly.
Prerequisites
- Java
- Node.js
- JDK 16 (recommended)
- A reference to an instance of the Integration Services API
- Maven
- A recent version of Node.js (>v16.17.0).
Required Packages
- Java
- Node.js
Ideally every dependency listed in the
project's POM (mvn clean install
). This example uses in
particular:
org.apache.commons.codec.digest
org.apache.http.*
org.bitcoinj.core.Base58
org.bouncycastle.crypto
org.json.*
This example uses Axios
as the HTTP client, bs58
to decode Base58, and @noble/ed25519 to sign the nonce. However, you can use any package as long as it accomplishes the same result.
Newer versions may work, but the examples were tested with the following versions:
- npm
- Yarn
npm install @noble/ed25519@1.5.1
npm install bs58@4.0.1
npm install axios@0.21.4
yarn add @noble/ed25519@1.5.1
yarn add bs58@4.0.1
yarn add axios@0.21.4
Authentication Workflow
1. Request a Nonce
First, request a nonce and supply your identity id.
You can find your current API version using the http://localhost:3000/info endpoint. This example uses v0.1
.
Never save your secret key in plain text in your code. Use local environment variables or IOTA Stronghold to store your secret keys securely.
- Java
- Node.js
private String createNonce(String didId)
throws IOException, URISyntaxException, ParseException, InvalidAPIResponseException {
final String endpoint = "authentication/prove-ownership/" + didId;
JSONObject response = sendIOTAGetRequest(endpoint, null, false);
return response.getString("nonce");
}
public JSONObject sendIOTAGetRequest(String endpoint, Map<String, String> params, boolean withAuth)
throws ClientProtocolException, IOException, URISyntaxException, ParseException,
InvalidAPIResponseException {
CloseableHttpClient client = HttpClients.createDefault();
final HttpEntity response_body = sendGetRequest(endpoint, params, withAuth, null, client); // see last step of tutorial for implementation of this method
if (response_body == null) {
return null;
}
JSONObject result = new JSONObject(EntityUtils.toString(response_body, StandardCharsets.UTF_8));
client.close();
return result;
}
import axios from 'axios';
const requestNonce = async () => {
const identityId = 'did:iota:8BAmUqAg4aUjV3T9WUhPpDnFVbJSk16oLyFq3m3e62MF';
const url = `http://localhost:3000/api/v0.2/authentication/prove-ownership/${identityId}`;
const request = await axios.get(url);
console.log(request.data);
};
requestNonce();
The returned response body will look like this .js object.
{
nonce: '3eaf8814caa842d94fdb96fc26d02f7c339e65ff';
}
2. Hash the Nonce
- Java
- Node.js
After you have retrieved the nonce, you should hash it. You can use insert java method to hash the nonce with the SHA-256 hash function and convert it to hexadecimal.
public void hashAndSignNonce(String privateKey, String publicKey, String nonce, String didId)
throws IOException, CryptoException, URISyntaxException, InvalidAPIResponseException {
byte[] b58key = Base58.decode(privateKey); // Decode a base58 key and encode it as hex key
String b58keyHex = DatatypeConverter.printHexBinary(b58key).toLowerCase();
byte[] convertKey = DatatypeConverter.parseHexBinary(b58keyHex);
String hashNonceHex = DigestUtils.sha256Hex(nonce); // Hash a nonce with SHA-256 (apache_commons)
byte[] convertNonce = DatatypeConverter.parseHexBinary(hashNonceHex);
[...]
After you have retrieved the nonce, you should hash it. You can use the Node API's createHash() function to hash the nonce with the SHA-256 hash function and convert it to hexadecimal.{#hashupdatedata-inputencoding) function to hash the nonce with the sha-256 hash function and convert it to hexadecimal.-node}
import crypto from 'crypto';
const hashNonce = () => {
const nonce = '3eaf8814caa842d94fdb96fc26d02f7c339e65ff';
const hashedNonce = crypto.createHash('sha256').update(nonce).digest('hex');
console.log(hashedNonce);
};
hashNonce();
The example's nonce will generate the following hash:
6d748f209e5af1f5b8825f7822d6659c45c874076cd2b3337c7861fd94cd3ba5
3. Sign the Hashed Nonce
Your secret key is encoded in Base58 and has to be decoded. Once it has been decoded, the nonce is signed with your encoded secret key and saved as a hexadecimal string.
Never save your secret key in plain text in your code. Use local environment variables or IOTA Stronghold to store your secret keys securely.
- Java
- Node.js
Ed25519PrivateKeyParameters privateKeyParams = new Ed25519PrivateKeyParameters(convertKey, 0); // Encode in
// PrivateKey
Signer signer = new Ed25519Signer(); // Sign a nonce using the private key
signer.init(true, privateKeyParams);
signer.update(convertNonce, 0, convertNonce.length);
byte[] signature = signer.generateSignature();
String sign = Da
[..tatypeConverter.printHexBinary(signature).toLowerCase();
[...]
import * as ed from '@noble/ed25519';
import bs58 from 'bs58';
const signNonce = async () => {
const hashedNonce =
'6d748f209e5af1f5b8825f7822d6659c45c874076cd2b3337c7861fd94cd3ba5';
const secretKey = '5N3SxG4UzVDpNe4LyDoZyb6bSgE9tk3pE2XP5znXo5bF';
const encodedSecretKey = bs58.decode(secretKey).toString('hex');
const signedNonceArray = await ed.sign(hashedNonce, encodedSecretKey);
const signedNonce = ed.Signature.fromHex(signedNonceArray).toHex();
console.log(signedNonce);
};
signNonce();
The example's nonce and secret key will generate the following signed nonce:
270c2e502c5c753e39159683981e452444f81a10d798f56406a9c471d672a5ede1792cb7f97d4f9c9efeec7bf35577dd1f8482afca7e3710291868a65bf91e07
4. Request the JWT
The last step is to request the JWT. You can use the following code snippet with your signed nonce in the request body.
- Java
- Node.js
final String endpoint = "authentication/prove-ownership/" + didId;
JSONObject json = new JSONObject().put("signedNonce", sign);
JSONObject response = sendIOTAPostRequest(endpoint, json, false);
byte[] b58keyPrimary = Base58.decode(publicKey);
String b58keyPrimaryHex = DatatypeConverter.printHexBinary(b58keyPrimary).toLowerCase();
byte[] convert_primarykey = DatatypeConverter.parseHexBinary(b58keyPrimaryHex);
Ed25519PublicKeyParameters primaryKeyVerify = new Ed25519PublicKeyParameters(convert_primarykey, 0);
Signer verifier = new Ed25519Signer();
verifier.init(false, primaryKeyVerify);
verifier.update(convertNonce, 0, convertNonce.length);
boolean verified = verifier.verifySignature(signature);
System.out.println("Verify Signature: " + verified);
this.jwt = response.getString("jwt");
}
import axios from 'axios';
const requestJWT = async () => {
const identityId = 'did:iota:8BAmUqAg4aUjV3T9WUhPpDnFVbJSk16oLyFq3m3e62MF';
const body = {
signedNonce:
'270c2e502c5c753e39159683981e452444f81a10d798f56406a9c471d672a5ede1792cb7f97d4f9c9efeec7bf35577dd1f8482afca7e3710291868a65bf91e07',
};
const url = `http://localhost:3000/api/v0.2/authentication/prove-ownership/${identityId}`;
const request = await axios.post(url, body);
console.log(request.data);
};
requestJWT();
The returned JS object will contain the following JWT:
{
jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiZGlkOmlvdGE6OEJBbVVxQWc0YVVqVjNUOVdVaFBwRG5GVmJKU2sxNm9MeUZxM20zZTYyTUYiLCJwdWJsaWNLZXkiOiI3WFRYVlJ5M0cxTVhjbURrejJiUUNiV3B2OEF6b1FSZ3hHdjVtRG0xRkoxdCIsInVzZXJuYW1lIjoiVGltMTIzNDUiLCJyZWdpc3RyYXRpb25EYXRlIjoiMjAyMi0wMi0xOFQwNzo0ODo0NSswMTowMCIsImNsYWltIjp7InR5cGUiOiJQZXJzb24ifSwicm9sZSI6IlVzZXIifSwiaWF0IjoxNjQ1MTc3OTg1LCJleHAiOjE2NDUyNjQzODV9.-O2UpPyfWOvtLV2cUF9fPVhgCGDCVwFU9zXrpn_uKU0';
}
5. Set the JWT as Request Header
This is an example of a GET request to the API with the JWT from the last step included in the Authorization
:
- Java
- Node.js
private HttpEntity sendGetRequest(String endpoint, Map<String, String> params, boolean needsBearer,
String presharedKey, CloseableHttpClient client) throws URISyntaxException, ClientProtocolException,
IOException, ParseException, InvalidAPIResponseException {
URIBuilder builder = new URIBuilder(this.baseUrl + endpoint);
if (params != null) {
for (Map.Entry<String, String> e : params.entrySet()) {
builder.setParameter(e.getKey(), e.getValue());
}
}
if (presharedKey != null) {
builder.setParameter("preshared-key", presharedKey);
}
builder.setParameter("api-key", this.apiKey);
final URI urlFinal = builder.build();
System.out.println("GET " + urlFinal.toString());
HttpGet httpGet = new HttpGet(urlFinal);
httpGet.setHeader(HttpHeaders.ACCEPT, "application/json");
if (needsBearer && this.jwt != null) {
httpGet.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + this.jwt);
}
CloseableHttpResponse response = client.execute(httpGet);
final HttpEntity response_body = response.getEntity();
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != 200) {
throw new InvalidAPIResponseException(
statusCode + EntityUtils.toString(response_body, StandardCharsets.UTF_8));
}
return response_body;
}
import axios from 'axios';
const setAxiosHeader = () => {
const jwt =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoiZGlkOmlvdGE6OEJBbVVxQWc0YVVqVjNUOVdVaFBwRG5GVmJKU2sxNm9MeUZxM20zZTYyTUYiLCJwdWJsaWNLZXkiOiI3WFRYVlJ5M0cxTVhjbURrejJiUUNiV3B2OEF6b1FSZ3hHdjVtRG0xRkoxdCIsInVzZXJuYW1lIjoiVGltMTIzNDUiLCJyZWdpc3RyYXRpb25EYXRlIjoiMjAyMi0wMi0xOFQwNzo0ODo0NSswMTowMCIsImNsYWltIjp7InR5cGUiOiJQZXJzb24ifSwicm9sZSI6IlVzZXIifSwiaWF0IjoxNjQ1MTc3OTg1LCJleHAiOjE2NDUyNjQzODV9.-O2UpPyfWOvtLV2cUF9fPVhgCGDCVwFU9zXrpn_uKU0';
axios.defaults.headers.common['Authorization'] = `Bearer ${jwt}`;
};
setAxiosHeader();
Putting It All Together
You can find the complete code example here. All snippets above are taken from there.
- Java
- Node.js
You can find the complete code example in this repository. All snippets above are taken from there.
The following snippet is the final code using all functions together to request a JWT. You can find the complete code example at this repository)
import axios from 'axios';
import * as ed from '@noble/ed25519';
import bs58 from 'bs58';
import crypto from 'crypto';
const requestNonce = async (identityId) => {
const url = `http://localhost:3000/api/v0.2/authentication/prove-ownership/${identityId}`;
const request = await axios.get(url);
return request.data.nonce;
};
const hashNonce = (nonce) => {
const hashedNonce = crypto.createHash('sha256').update(nonce).digest('hex');
return hashedNonce;
};
const signNonce = async (hashedNonce, secretKey) => {
const encodedSecretKey = bs58.decode(secretKey).toString('hex');
const signedNonceArray = await ed.sign(hashedNonce, encodedSecretKey);
const signedNonce = ed.Signature.fromHex(signedNonceArray).toHex();
return signedNonce;
};
const requestJwt = async (identityId, signedNonce) => {
const url = `http://localhost:3000/api/v0.2/authentication/prove-ownership/${identityId}`;
const request = await axios.post(url, { signedNonce });
return request.data.jwt;
};
const setAxiosHeader = (jwt) => {
axios.defaults.headers.common['Authorization'] = `Bearer ${jwt}`;
};
const authenticate = async (identityId, secretKey) => {
const nonce = await requestNonce(identityId);
const hashedNonce = hashNonce(nonce);
const signedNonce = await signNonce(hashedNonce, secretKey);
const jwt = await requestJwt(identityId, signedNonce);
setAxiosHeader(jwt);
};
const identityId = 'did:iota:8BAmUqAg4aUjV3T9WUhPpDnFVbJSk16oLyFq3m3e62MF';
const secretKey = '5N3SxG4UzVDpNe4LyDoZyb6bSgE9tk3pE2XP5znXo5bF';
authenticate(identityId, secretKey);