Event authorization in JS SDK with Flask
In the digital age, understanding and engaging with your customers on a personalized level has become a pivotal factor in business success. Synerise provides an efficient solution to this with its advanced tracking capabilities. By implementing the authorization of events with the Synerise tracking script, businesses can open doors to a plethora of opportunities.
Employing JWT (JSON Web Tokens) authentication for these events ensures data security, giving businesses the confidence in knowing their customer data is protected. JWT, being a compact and self-contained way for securely transmitting information, is an industry-standard for authorization. Synerise’s insistence on using JWT with the RS256 algorithm provides an additional layer of security.
In essence, authorizing events with Synerise is not just about sending and receiving data. It’s about transforming the way businesses interact with their customers, providing them with unique, personalized experiences, all the while ensuring the utmost data security. This integration is an essential step for companies aiming for growth, increased customer satisfaction, and improved ROI.
In our step-by-step guide to authorizing events with Synerise, we’ll be leveraging two prominent technologies: Python Flask and JavaScript React. Flask is a lightweight web framework for Python, ideal for creating small to medium-sized web applications with ease and efficiency. On the other hand, React is a renowned JavaScript library developed by Facebook, designed for building user interfaces in a modular and reactive manner. Together, Flask will power our backend, providing necessary endpoints and logic, while React will drive our frontend, ensuring a seamless user experience. This combination allows for a robust and scalable solution, optimized for both development and production scenarios.
After you complete this integration and your website is ready to authorize events with JWT, you can start requiring authorization for sensitive events. For details on enabling JWT requirements for events, see “Event authentication settings”.
Prerequisites
- Public RSA key added to Synerise. If keys were not added before, check how you can add them here.
- Local machine with installed Python and Node environments.
- Tracking code added to your React website.
- Basic skills in React and Python.
Process
The logic is described in Authenticating requests with JSON Web Tokens (JWT) and this use case presents an example implementation step by step.
Implement back-end
Before creating the front-end, we must create a backend—our bridge to the Synerise platform. The PyJWT and cryptography libraries ensure that a secure JWT is created according to Synerise’s standards. The primary endpoint, /generate-jwt (created with Flask), facilitates this by generating a JWT from customer data.
Install libraries
Using the pip install Flask PyJWT cryptography
command, install three Python packages: Flask, PyJWT, and cryptography.
Explanation of these packages:
- Flask - Lightweight web framework for Python. We will use it to create the web server and define the API endpoint to generate JWT and return it to our React application.
- PyJWT - Python library which allows encoding and decoding JSON Web Tokens (JWT).
- cryptography - Python library that offers secure cryptographic operations, essential for RS256 algorithm support in generating JWTs in our project.
Implement the Flask application
The application imports the private RSA key, which is needed to sign the JWT.
The /generate-jwt endpoint of the application accepts POST requests with customer information (email and UUID) and uses that data to generate a JWT compliant with Synerise’s requirements.
Error handling is included in the code. To better understand the code, read the comments.
from flask import Flask, jsonify, request
import jwt
import datetime
app = Flask(__name__)
# Load the RSA private key for JWT signing.
# Synerise requires JWTs to be signed with RSA.
with open('private.pem', 'r') as f:
PRIVATE_KEY = f.read()
@app.route('/generate-jwt', methods=['POST'])
def generate_jwt():
"""
Endpoint to generate JWT using the RS256 algorithm, as required by Synerise. This method expects a JSON payload containing the "email" and "uuid" of a customer.
"""
# Retrieve the JSON payload from the request
data = request.get_json()
# Ensure both 'email' and 'uuid' are present in the request
# As per documentation, the JWT payload should include customer's email and UUID.
if not data or 'email' not in data or 'uuid' not in data:
return jsonify({'error': 'Missing email or uuid in request'}), 400
email = data['email']
uuid = data['uuid']
# JWT header as defined in the documentation
# Synerise requires the JWT to use the RS256 algorithm.
headers = {
"alg": "RS256",
"typ": "JWT"
}
# JWT payload as per Synerise's requirements
payload = {
"exp": datetime.datetime.utcnow() + datetime.timedelta(days=7), # Token expiry set to 7 days as stated in the documentation
"uuid": uuid, # customer's UUID
"email": email # customer's email
}
# Generate the JWT token
# The token is signed with the RSA private key as required by the documentation.
token = jwt.encode(payload, PRIVATE_KEY, algorithm="RS256", headers=headers)
return jsonify({'jwt': token})
if __name__ == '__main__':
# Start the Flask application
app.run(debug=True)
Implement front-end
The front-end part is modular to ensure efficiency and easier maintenance.
The useSyneriseAuthentication hook creates an UUIDv5 for the customer and uses it in the request for a JWT.
Next, the hook is used in the LoginForm component, which collects the customer’s email that’s passed to the hook for creating a JWT. To better understand the code, read the comments.
Implement custom authentication hook
import { useState } from 'react';
/**
* Custom hook to handle Synerise authentication.
*/
function useSyneriseAuthentication() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// Function to generate UUIDv5 based on a salt and unique identifier
const generateUUIDv5 = (uniqueIdentifier) => {
const salt = "someString";
return uuid.uuid5(uuid.NAMESPACE_URL, (salt + uniqueIdentifier));
};
// Function to fetch JWT from the backend
const fetchJWT = async (email, uuidValue) => {
const response = await fetch('/generate-jwt', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, uuid: uuidValue }),
});
if (!response.ok) {
throw new Error("Failed to fetch JWT");
}
const { jwt } = await response.json();
return jwt;
};
// Function to authenticate a user with Synerise
const authenticate = async (email) => {
setLoading(true);
setError(null);
try {
const existingIdentityHash = SR.client.getIdentityHash();
const hashedEmail = SR.client.hashIdentity(email);
let uuidValue;
if (!existingIdentityHash || hashedEmail !== existingIdentityHash) {
uuidValue = generateUUIDv5(email); // Derive from a cookie in a real-world scenario
const jwt = await fetchJWT(email, uuidValue);
SR.client.setUuidAndidentityHash(hashedEmail, uuidValue);
SR.client.setAccessToken(jwt);
} else {
// Identity matches the current customer; Continue with the existing JWT
// This can be enhanced based on specific requirements
}
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
};
return { authenticate, loading, error };
}
export default useSyneriseAuthentication;
Implement LoginForm component
Below you can find the example of useSyneriseAuthentication usage:
import { useState } from 'react';
import useSyneriseAuthentication from './useSyneriseAuthentication'; // Ensure you've created this hook as mentioned earlier.
function LoginForm() {
const [email, setEmail] = useState('');
const syneriseAuth = useSyneriseAuthentication();
const handleSubmit = async (event) => {
event.preventDefault();
// Initiate authentication process using the provided email
const success = await syneriseAuth.authenticate(email);
if (success) {
console.log("Authentication successful.");
// Here, you can redirect or update the UI as necessary.
} else {
console.log("Authentication failed.");
// Handle failure, maybe show an error message or retry.
}
};
return (
<div>
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
required
/>
</div>
<div>
<button type="submit">Login</button>
</div>
</form>
</div>
);
}
export default LoginForm;
Test the solution and adapt it to fit your specific needs. For example, we recommend adding authentication on your website before a visitor can request the JWT and start sending events.
What’s next
Send events from your website as described in “Event tracking”.