-
Notifications
You must be signed in to change notification settings - Fork 263
Using the Backpack Connect API
As an issuer, you can push earner badges to the Mozilla hosted Backpack with their permission. You can do this using the Issuer API or using Backpack Connect. This guide demonstrates using the Backpack Connect approach to send earner badges to their Backpacks.
With the Issuer API, the user must grant permission every time you attempt to push to their Backpack. With Backpack Connect, you can manage user permission on an ongoing basis, using a persistent session. Once the earner has granted you permission to push badges to their Backpack, you will be able to do so without their interaction unless they revoke your permission, which they can do at any time.
To manage your interaction with the earner Backpack, the Connect API uses access tokens.
In order to use the Issuer API, you must have your badge assertions stored as hosted files (at a stable location) or as JSON Web signatures. To check your assertions for validity, use the Validator. To find out more about assertion structure, see the current assertion spec. If you're new to Open Badges, check out Open Badges Onboarding: Issuers and Assertion Information for the Uninitiated.
- Prepare your assertions
- Get user permission
- Callback
- Maintain access
- Push to the Backpack
- Identity
- Notes
Before you attempt to push an earner badge to the Backpack, prepare your assertions - the Connect API accepts both signed and hosted assertions.
Note: The url
field in your issuer organization JSON (linked to from the badge class issuer
field) must indicate the same domain as the location you request access to the Backpack Connect API from. For example, if you request access to the Connect API for http://example.org
, you will only be able to push badges whose issuer url
field also indicates http://example.org
.
The earner will see your domain listed in their Backpack Settings, together with the level of access they have granted you.
As with the standard Issuer API, your hosted assertions should be at stable URLs accessible via HEAD
and GET
.
You can validate your assertions (signed or hosted) at the Validator.
Note: Some proposed changes to the assertion specification are currently under discussion. See these threads in particular:
To use the Connect API, you first need to get the earner permission. Include the issuer script in your site or application code:
<script src="https://backpack.openbadges.org/issuer.js"></script>
You can then call the connect
method to request permission to access the earner's Backpack, indicating the scope of what you plan on doing with it - in this case issue
, which will allow you to push badges to the Backpack:
function connectToBackpack(){
OpenBadges.connect({
callback: "http://yoursite.com/callback",
scope: ['issue']
});
}
When you call this JavaScript function, the browser will redirect the earner to the Backpack, with the following dialog asking them to log in if necessary:
Once signed in, the earner is presented with a dialog asking their permission for your domain to access their Backpack:
The earner can manage issuer permission to access their Backpack in the Settings section, revoking access whenever they choose:
When the earner grants permission (or denies it), the browser sends data to the callback
you included when you called the connect
method. At the location you specified, you can retrieve the GET
parameters sent from the Backpack to your URL, which include:
Parameter | |
---|---|
error |
If the user denied the issuer access to their backpack, this will be set to access_denied . |
access_token |
A string you can use to access the user's Backpack. |
refresh_token |
A string that can be used to obtain a new access token whenever yours expires. |
expires |
The number of seconds that the access token is valid before it needs to be refreshed. |
api_root |
The absolute URL pointing to the root of the user's backpack connect API endpoint. Note that this won't necessarily point to the openbadges.org domain, since a user's Backpack may be located anywhere on the Web. |
See the following node.js example:
app.get('/callback', function(req, res){
var error = req.param('error');
var access = req.param('access_token');//use this to push to the earner Backpack
var refresh = req.param('refresh_token');
var expiry = req.param('expires');
var api = req.param('api_root');
//...
});
When you connect for the first time, you will receive an access token and a refresh token for when the access token expires. Each time you refresh your token, you will receive another access token and another refresh token to use next time.
Each token you receive for accessing the Backpack will only last a short period of time. For example, you may receive an expires
value of 3600
, meaning that you have 1 hour to use the token. If your token has expired, you can request a new one using the refresh_token
you will receive each time you receive an access token.
If you attempt to use the API when your token has expired, you will receive an invalid_token
error.
To refresh your token, use the token
endpoint at the location you received as api_root
, passing the refresh_token
and grant_type
parameters as in the following node.js example:
var tokenData = querystring.stringify({
grant_type: 'refresh_token',
refresh_token: 'your-refresh-token-value'
});
//where api root is backpack.openbadges.org/api
var requestOptions = {
host : 'backpack.openbadges.org',
path : '/api/token',
method : 'POST',
headers: {'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(tokenData)
}
};
var postRequest = http.request(requestOptions, function(refreshResponse) {
var response = [];
refreshResponse.setEncoding('utf8');
//store data
refreshResponse.on('data', function (responseData) {
response.push(responseData);
});
refreshResponse.on('end', function(){
var refreshData=JSON.parse(response.join(''));
//...
});
});
postRequest.on('error', function(e) {
console.error(e);
});
// post the data
postRequest.write(tokenData);
postRequest.end();
The response will contain the following JSON structure:
{
"expires": 3600,
"access_token": "new-access-token",
"refresh_token": "new-refresh-token"
}
The data items are as follows:
Field | |
---|---|
expires |
Seconds before the new token expires. |
access_token |
Your new access token. |
refresh_token |
Your new refresh token. |
When you have your tokens organized, you can push earner badges to the Backpack without their interaction. Use the issue
endpoint at the api_location
you received from the connect
method.
When pushing to the Backpack, include the URL or signature of the assertion as badge
parameter.
You also need to authenticate your attempt to push to the Backpack by including the Base64 encoding of your access token as Bearer
in the Authorization
header of your HTTP request.
See the following node.js example for a hosted badge assertion:
var assertionData = querystring.stringify({
badge: 'http://yoursite.com/badge-assertion.json'
});
var requestOptions = {
host : 'backpack.openbadges.org',//adjust for your api root
path : '/api/issue',
method : 'POST',
headers: { 'Authorization': 'Bearer ' + b64enc('your-access-token'),
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(assertionData)
}
};
var postRequest = http.request(requestOptions, function(pushResponse) {
var response = [];
pushResponse.setEncoding('utf8');
//store data
pushResponse.on('data', function (responseData) {
response.push(responseData);
});
pushResponse.on('end', function(){
var pushData=JSON.parse(response.join(''));
//...
});
});
postRequest.on('error', function(e) {
console.error(e);
});
// post the data
postRequest.write(assertionData);
postRequest.end();
This alternative would apply to a signed assertion:
/*build sigature*/
var assertion = {
"uid": "assertion-uid",
"recipient": {
"identity": "sha256$hashed-email",
"type": "email",
"hashed": true
},
"badge": "http://yoursite.com/badge-class.json",
"verify": {
"url": "http://yoursite.com/public.pem",
"type": "signed"
},
"issuedOn": 1403784577
};
const signature = jws.sign({
header: {alg: 'rs256'},
payload: assertion,
privateKey: fs.readFileSync(__dirname + '/private.pem')//if the private key is in this directory
});
/*as above except badge field is signature string instead of url*/
var signatureData = querystring.stringify({
badge: signature
});
var requestOptions = {
host : 'backpack.openbadges.org',//adjust for your api root
path : '/api/issue',
method : 'POST',
headers: { 'Authorization': 'Bearer ' + b64enc('your-access-token'),
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(signatureData)
}
};
var postRequest = http.request(requestOptions, function(pushResponse) {
var response = [];
pushResponse.setEncoding('utf8');
//store data
pushResponse.on('data', function (responseData) {
response.push(responseData);
});
pushResponse.on('end', function(){
var pushData=JSON.parse(response.join(''));
//...
});
});
postRequest.on('error', function(e) {
console.error(e);
});
// post the data
postRequest.write(signatureData);
postRequest.end();
The Backpack will respond with either success or failure detail. If successful, your response should have the following example JSON structure:
{
"exists": false,
"badge": {
"uid": "assertion-uid",
"recipient": "sha256$hashed-email",
"badge": {
"name": "Badge Name",
"description": "Badge description.",
"image": "http://yoursite.com/badge-image.png",
"criteria": "http://yoursite.com/criteria.html",
"alignment": [ ],
"issuer": {
"name": "Issuer Name",
"url": "http://yoursite.com",
"_location": "http://yoursite.com/issuer-organization.json",
"origin": "http://yoursite.com"
},
"_location": "http://yoursite.com/badge-class.json"
},
"verify": {
"url": "http://yoursite.com/public.pem",
"type": "signed"
},
"issuedOn": 1403784577,
"_originalRecipient": {
"identity": "sha256$hashed-email",
"type": "email",
"hashed": true
},
"issued_on": 1403784577
}
}
If your attempt to push the earner badge to the Backpack was unsuccessful, your response will have the following example structure:
{
"message": "issuer origin must be identical to bearer token origin"
}
This error message would apply where your issuer organization url
does not match the domain for the access token you are using.
You will receive an error response in any of the following circumstances:
- the assertion is malformed/ invalid
- no assertion URL or signature is passed
- the assertion is for a different earner
- issuer origin does not match access token origin
- the badge has already been added to the earner Backpack
- your access token has expired
- the access token is invalid
The Connect API also provides the identity
endpoint, which allows you to query the Backpack for the current earner's hashed email address. This allows you to issue to the earner without even knowing their email address. See the following node.js example:
var requestOptions = {
host : 'backpack.openbadges.org',//adjust for your api root
path : '/api/identity',
method : 'GET',
headers: { 'Authorization': 'Bearer ' + b64enc('your-access-token') }
};
var idRequest = http.request(requestOptions, function(idResponse) {
var response = [];
idResponse.setEncoding('utf8');
//store data
idResponse.on('data', function (responseData) {
response.push(responseData);
});
idResponse.on('end', function(){
var idData=JSON.parse(response.join(''));
//...
});
});
idRequest.on('error', function(e) {
console.error(e);
});
idRequest.end();
The API will respond with the following structure:
{
"recipient": "sha256$hashed-email",
"salt": "_123abc456",
"type": "email"
}
You could then use the returned hash of the user email to issue a badge to them - the recipient
value can be placed directly into your badge assertion JSON as the recipient
identity
value, with type
set to email
, hashed
set to true
and salt
set to the received salt
value.
The Connect API is designed in part to accommodate a future in which there may be multiple Open Badges Backpacks - see also the Federated BadgeKit Backpack which is currently under development.
You are encouraged to use HTTPS
in your issuing site, although this is not enforced - it is recommended to ensure that your authorization credentials remain confidential.
The API supports Cross Origin Resource Sharing (CORS) for AJAX requests. You can read the CORS W3C working draft or the MDN HTTP Access Control page for more information.
Remember to use application/json
as Content-Type
for your POST
requests.
The Issuer API is an alternative, less complex way to push earner badges to the Backpack - it requires the earner to grant permission each time but involves much less coding on the issuer site.