Zenodo Integration: Protecting User Privacy While Enhancing Experience
Written on
> “Privacy protection is a key element of customer confidence and a pillar of sustainable digital business development.” > — Stéphane Nappo
Open Access refers to the practice of making research data available to everyone. By doing so, your datasets become easier to find, increasing awareness among other researchers and significantly boosting the impact of your work. Zenodo serves as a platform that facilitates the sharing, curation, and publication of data and software for all researchers.
Focusing on our project, our team made the decision to incorporate Zenodo into one of our offerings. We are developing an Open Reuse platform, an Open-Source initiative aimed at enabling researchers to share their efforts in reusing and reproducing published research, thereby enhancing its visibility.
To streamline the process, our team decided to allow researchers to publish their data related to reuse and reproducibility efforts directly on Zenodo through the OpenReuse platform, eliminating the need to log in separately. Zenodo offers a comprehensive API that facilitates easy integration for developers looking to incorporate its functionalities into their workflows.
> Our main challenge was ensuring robust privacy for users, a principle our team prioritizes. We were not inclined to store personal details such as contact information or login credentials from third-party tools in our database, as this would provide us with unnecessary access to users' Zenodo accounts.
The solution to this challenge was straightforward: we opted for a Frontend approach.
Zenodo provides a Python guide for utilizing its REST API, which assists third-party developers in backend integration while requiring user tokens for authentication. However, since we aimed to avoid sending user credentials to our servers, we chose to implement it using jQuery on the client side.
Here’s the simplified process we followed, which could help others interested in integrating Zenodo into their applications with jQuery.
Practical Demonstration
Include an option for users to log in via Zenodo.
Upon clicking the login button, the Zenodo OAuth API is triggered, authenticating the user and providing an access token. This token functions similarly to a standard OAuth access token for API authentication. Here’s how to call the Zenodo authentication API:
<a target="_blank" href="https://zenodo.org/oauth/authorize?scope=deposit:write+deposit:actions&state=CHANGEME&redirect_uri=<redirect_page_uri>&response_type=code&client_id=<client_id>">
Login With Zenodo
</a>
The href attribute makes a GET request to the Zenodo OAuth API (https://zenodo.org/oauth/authorize) with the following parameters:
- scope: Specifies permissions for the personal access token to limit data and action access in Zenodo. Possible scope values include:
- deposit:actions - Allows publishing uploads.
- deposit:write - Allows uploads (not publishing).
- user:email - Grants access to the email address (read-only).
- state: ‘CHANGEME’
- response_type: ‘code’
- client_id: <your_own_client_id>
- redirect_uri: <your_own_redirect_uri>: This is the URL in your application where Zenodo redirects users post-authentication (e.g., https://openreuse.org/zenodo_callback.html). This page can display a loader while executing the script to retrieve the access token.
// zenodo_callback.html
$.ajax({
url: '/backend/zenodo_token_callback',
dataType: 'json',
data: {
'code': <your_own_code> //described below
},
success: function(data) {
localStorage.setItem("zenodo_application_token", data.access_token);
window.close();
}
});
- scope: Specifies permissions for the personal access token to limit data and action access in Zenodo. Possible scope values include:
> Note: The API called in our backend prevents revealing the client secret to users. We also do not store the access token on the backend, merely returning the response to the client side. Therefore, users will need to re-authorize themselves upon each login. Call the API (https://zenodo.org/oauth/token) on the backend to obtain the access token in zenodo_callback.html. I'm using Python here, but you can use any programming language of your choice.
@app.route('/backend/zenodo_token_callback')
def zenodo_callback():
code = request.args.get('code', '')
r = requests.post("https://zenodo.org/oauth/token",
data={
'client_id': <your_own_client_id>,
'client_secret': <your_own_client_secret>,
'grant_type': 'authorization_code',
'code': <your_own_code>,
'redirect_uri': <your_own_redirect_uri>
}
)
data = r.json()
return app.response_class(
response=json.dumps(data),
status=200,
mimetype='application/json'
)
The client_id and client_secret are obtained after registering for a developer account on Zenodo. The code is derived from the redirect page URL parameter.
Flow Overview
- Click the Login button.
- The Zenodo OAuth API is called, and an authorization window appears.
- Click the ‘Authorize Application’ button.
- This redirects you to the URL provided in the redirect_uri, appending the code to the URL (e.g., https://openreuse.org/zenodo_callback.html?code=W6qVexybyUio8mHzU4FBy8W51XONS).
- Retrieve the code from the URL on the redirected page and make an AJAX call using this code to receive the access token.
- Store the access token in local storage for further authentication.
- Close the window and return to your application page.
With the access token saved in local storage, call the Zenodo API to create a new upload.
access_token = localStorage.getItem("zenodo_application_token");
var articleData = {
'metadata': {
'title': 'My First Article',
'upload_type': 'dataset',
'description': 'This is my first upload'
}
};
$.ajax({
type: "POST",
url: "https://zenodo.org/api/deposit/depositions",
data: JSON.stringify(articleData),
dataType: 'json',
async: true,
beforeSend: function(xhr) {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
},
success: function(data) {
deposition_id = data['id'];
}
});
The response from the API call will look like this:
{
"conceptrecid": "1419271",
"created": "2018-09-14T19:38:05.283385+00:00",
"files": [],
"id": 1419272,
"links": {
"bucket": "https://zenodo.org/api/files/c22c9a33-8886-4cfc-a44a-bf27ae6469be",
"discard": "https://zenodo.org/api/deposit/depositions/1419272/actions/discard",
"edit": "https://zenodo.org/api/deposit/depositions/1419272/actions/edit",
"files": "https://zenodo.org/api/deposit/depositions/1419272/files",
"html": "https://zenodo.org/deposit/1419272",
"latest_draft": "https://zenodo.org/api/deposit/depositions/1419272",
"latest_draft_html": "https://zenodo.org/deposit/depositions/1419272",
"publish": "https://zenodo.org/api/deposit/depositions/1419272/actions/publish",
"self": "https://zenodo.org/api/deposit/depositions/1419272"
},
"metadata": {
"access_right": "open",
"creators": [
{
"affiliation": "Zenodo",
"name": "Manisha"
}
],
"description": "This is my first upload",
"license": "CC0-1.0",
"prereserve_doi": {
"doi": "10.5281/zenodo.1419272",
"recid": 1419272
},
"publication_date": "2018-09-14",
"title": "My First Article",
"upload_type": "dataset"
},
"modified": "2018-09-14T19:38:05.283393+00:00",
"owner": 48521,
"record_id": 1419272,
"state": "unsubmitted",
"submitted": false,
"title": "My First Article"
}
From the API response, extract the article ID and upload the file data using this ID.
access_token = localStorage.getItem("zenodo_application_token");
var deposition_id = data['id']; // From the previous API call
var file = $('input[type=file]')[0].files[0];
var fd = new FormData();
fd.append('file', file);
$.ajax({
type: "POST",
url: "https://zenodo.org/api/deposit/depositions/" + deposition_id + "/files",
data: fd,
processData: false,
contentType: false,
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
},
success: function(response) {
// Uploaded file information
}
});
You can publish your article to make it publicly accessible.
> Caution: Be careful with this last step, as it will publish your test upload online. Once published and assigned a DOI, it cannot be removed.
var deposition_id = data['id']; // From the API call executed in the third step
$.ajax({
type: "POST",
url: "https://zenodo.org/api/deposit/depositions/" + zenodo_deposition_id + "/actions/publish",
beforeSend: function(xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + zenodo_token);},
success: function(data) {
console.log("Zenodo article published:", data);}
});
And that’s it! Your data is successfully uploaded to Zenodo.
Expectations and Outcomes:
- We aimed to avoid storing user credential tokens on our servers. By integrating Zenodo using jQuery on the client side, we achieved this goal without needing to transmit the access tokens to our servers. Tokens stored in local storage will expire once the session concludes.
- The application only accesses data that the user permits, and only while the session is active. If the user logs out and logs back in, they must re-authorize themselves.
Here’s a link to a demo for hands-on experience. If you're a researcher looking to have your reuse efforts recognized, please sign up on OpenReuse to explore this for yourself.
For code references related to this practical demonstration, visit this GitHub repository — Code Reference.
If you have any questions or suggestions, feel free to reach out to me.
References:
- https://scholarlykitchen.sspnet.org/2018/08/23/new-plugins-kopernio-unpaywall-pursuing/
- https://inc42.com/buzz/facebook-login-breached/
- https://ra21.org/index.php/what-is-ra21/ra21-position-statement-on-access-brokers/
- https://thehackernews.com/2017/02/password-manager-apps.html
- https://scholarlykitchen.sspnet.org/2016/05/19/sci-hub-and-academic-identity-theft-an-open-letter-to-university-faculty-everywhere/