22 March 2024
—
The Zucoin wallet app can pass signed wallet authentication data through to third-party websites and services.
The user starts this flow in the Zucoin wallet app, for example by opening a selected website or service inside an HTML iframe.
The link to the website includes additional URL query parameters (for example: ?aaa=bbb&ccc=ddd).
A website or service can check this incoming information and use it to authenticate a Zucoin wallet user.
For example, the result of this authentication can be used to sign a user into an existing account, create an account if one does not exist, and then load a dashboard, product page, or other point of interest.
Web developers can use the following code example to authenticate incoming data from the Zucoin wallet app.
Here's a simple example of how to do it:
/**
* Uses data in URL query variables to parse and validate an incoming
* user authentication request. Use the result to verify a user in a
* back-end system and/or redirect them to a desired endpoint
*
* @tip Load a sodium encryption library beforehand, e.g.
* https://github.com/jedisct1/libsodium.js, as it's not built into
* JavaScript
*
* @returns Object|bool An object of authentication data on success
* and false on failure
*/
function validate_incoming_user_authentication_request()
{
// Get URL query variables from current URL
const url_params = new URLSearchParams(window.location.search);
// Note URLSearchParams() auto applies decodeURIComponent()
const referral_source = url_params.get('utm_source');
if (!referral_source || !referral_source.includes('zucoin_wallet_app_v'))
{
// Not a Zucoin wallet app referral
return false;
}
const version = url_params.get('zucoin__data_pass_through__version');
if ('1' !== version)
{
// Unsupported data pass through version
return false;
}
const key_public__base64urlsafe = url_params.get('zucoin__data_pass_through__key_public__base64urlsafe');
// Will contain a timestamp in seconds
const timestamp_secs__raw = url_params.get('zucoin__data_pass_through__data_timestamp_secs');
if (
!timestamp_secs__raw ||
'string' !== typeof timestamp_secs__raw ||
!/^[0-9]+$/.test(timestamp_secs__raw)
) {
// Invalid timestamp secs provided
return false;
}
const timestamp_secs = parseInt(timestamp_secs__raw, 10);
const timestamp_secs_signature__base64urlsafe = url_params.get('zucoin__data_pass_through__data_timestamp_secs_signature__base64urlsafe');
if (!key_public__base64urlsafe || !timestamp_secs_signature__base64urlsafe)
{
// Invalid public key or signature provided
return false;
}
// Verify the signed data
let verified = false;
try
{
const signature__binary = sodium.from_base64(
timestamp_secs_signature__base64urlsafe,
sodium.base64_variants.URLSAFE
);
const key_public__binary = sodium.from_base64(
key_public__base64urlsafe,
sodium.base64_variants.URLSAFE
);
verified = sodium.crypto_sign_verify_detached(
signature__binary,
// Verify the exact string that the wallet signed
timestamp_secs__raw,
key_public__binary
);
}
catch (error)
{
// Invalid base64_urlsafe data provided
return false;
}
if (!verified)
{
// Cryptographic signature verification failed. Invalid details provided
return false;
}
return {
referral_source: referral_source,
version: version,
key_public__base64urlsafe: key_public__base64urlsafe,
timestamp_secs: timestamp_secs,
timestamp_secs_signature__base64urlsafe: timestamp_secs_signature__base64urlsafe,
};
}
You can then use the value of key_public__base64urlsafe as a user ID.
Developers should also check when the authentication data was cryptographically signed.
For example, login credentials from two weeks ago should fail. Future-dated timestamps should fail too.
You can use the following example to check if an authentication request is within 30 seconds of the current time:
// Check if the timestamp is outside the allowed window
function is_expired_incoming_user_authentication_request(auth_data)
{
const time_now_secs = Math.floor(Date.now() / 1000);
const time_passed_secs = Math.abs(time_now_secs - auth_data.timestamp_secs);
const expiry_time_secs = 30;
if (time_passed_secs > expiry_time_secs)
{
// Authentication data is outside the allowed time window, fail
return true;
}
// Authentication data is recent
return false;
}
// (Result from above)
function get_verified_incoming_user_authentication_request()
{
const zucoin_user_authenticated = validate_incoming_user_authentication_request();
if (!zucoin_user_authenticated)
{
// Stop, invalid
return false;
}
const expired = is_expired_incoming_user_authentication_request(
zucoin_user_authenticated
);
if (expired)
{
// Stop, invalid
return false;
}
return zucoin_user_authenticated;
}
Once verification succeeds, you can use this data, such as the user's public key, in your website or app to check whether a user exists and run the appropriate process.
If the user exists, you can take them to their home screen, account, or dashboard page.
If the user doesn't exist, create an entry in your database for them first, then take them to their home screen, account, or dashboard page.
That's it—let us know what you build with it!
Disclaimer: Of course, this is not advice, financial or otherwise. It’s also important to consider the risks and challenges associated with any potential benefits.
Get periodic insights, news, offers + more, trusted by 1,000+ readers.
(By subscribing you agree to receive news + marketing emails, but we won't spam or sell your data!)