-
Notifications
You must be signed in to change notification settings - Fork 263
Verifying Badges for Display
If you plan on displaying earner badges within a site, application or widget, you are encouraged to verify the badges first. In this guide we will work through the recommended verification steps - first, check what your options are:
- Are you retrieving the badges from the earner's Mozilla hosted Backpack via the Displayer API?
- Badges returned from the Backpack can be assumed valid so there's no need to carry out additional verification on them!
- Are you developing your display implementation in node.js?
- You can use the Validator to verify the badges in a few lines of code - see using the validator below.
- Otherwise you can verify badges for display by following the recommended steps.
You will need some familiarity with the JSON badge assertion structure to verify and/or parse the badges you display - if you're new to assertions see these resources:
To verify an earner badge, you need to:
- check that the badge assertion is structurally valid
- check the badge assertion's verification URL
- check that the badge has not been revoked - only for signed assertions
- verify the earner identity - which may have been hashed
- carry out JWS verification using a public key - only for signed assertions
- Using the Validator
- Verification Steps
- Badge Types
- Verifying Hosted Badges
- Verifying Signed Badges
- Checking Badge Structure
- Checking Earner Email Addresses
- Overview
- Notes
You can install the validator in a node.js app via npm as follows:
npm install mozilla/openbadges-validator
Then include it in your app:
var validator = require('openbadges-validator');
The validator provides a range of methods you can see on the repo readme. The following example works for both signed and hosted assertions:
//pass assertion JSON as parameter
validator(assertion, function(err, info){
if(err) console.log(err);
else if(info) console.log(JSON.stringify(info.version));//for demonstration
});
If validation fails, the err
parameter will include details in the following format:
{
"message":"Issued in error",
"code":"verify-revoked"
}
In this case the badge has been revoked.
If validation was successful, the validator will return several fields in the info
parameter, including:
-
version
: Version of the specification that the validated assertion corresponds to. -
guid
: The GUID of the assertion. -
signature
: JSON Web Signature representation of the assertion (if applicable) -
structures
: assertion
badge
issuer
resources
If you can't use the node.js validator and need to verify badges for display, you will need to implement a number of verification steps - tailored to the type of badge you are dealing with in each case.
Awarded badges can be either hosted or signed. With a hosted badge, the issuer hosts three JSON files representing:
- the badge assertion
- the badge class
- the issuer organization
The badge assertion includes a link to the badge class, which includes a link to the issuer organization. With a hosted badge, the issuer stores the badge assertion at a stable URL and validation will be performed via that URL.
With a signed badge, the issuer provides a JSON Web Signature representing the badge assertion - the badge class and issuer organization are still stored in hosted files. Validation for a signed badge is performed via the signature string.
The assertion structure is discussed in more detail here: Assertion Information for the Uninitiated
In order to verify a badge before displaying it, you will need to establish whether it is signed or hosted, as the verification processes are different. If you're retrieving a hosted badge for display you will be dealing with raw JSON (or a URL hosting a raw JSON file). With a signed badge, you will be dealing with a JSON Web Signature (JWS).
A badge assertion should have the following sample structure:
{
"uid": "f2c20",
"recipient": {
"type": "email",
"hashed": true,
"salt": "deadsea",
"identity": "sha256$c7ef86405ba71b85acd8e2e95166c4b111448089f2e1599f42fe1bba46e865c5"
},
"image": "https://example.org/badge-image.png",
"evidence": "https://example.org/evidence.html",
"issuedOn": 1359217910,
"badge": "https://example.org/the-badge-class.json",
"verify": {
"type": "hosted",
"url": "https://example.org/the-badge-assertion.json"
}
}
The verify.type
field indicates whether the badge is signed or hosted.
For a hosted badge, the verification process is as follows:
- checking that the
verify.url
resource is available - checking any included expiry date
- checking structural validity
- checking the earner email address.
To verify that the badge assertion is available, meaning that the JSON file representing the awarded badge is available online at the specified URL, displayers should perform a GET
request on the location listed in the badge data verify.url
field.
- If the location listed in the
verify.url
field does not eventually return a200 OK
status, the assertion should be considered invalid. - If the URL does return a
200 OK
status, displayers can proceed to check the badge structure and check the email address.
To check any expiry date set in the badge assertion, retrieve the timestamp in the expires
field and check it against the current date and time.
Signed badges involve the badge assertion being packaged into a JSON Web Signature. Verification of signed badges involves a few more steps than with hosted badges.
If you are receiving a badge assertion as a signature, you will need to decode it, unpacking the JWS payload in order to get at the badge assertion structure.
Your approach will depend on the technologies/ languages you are using in your display application. The following demonstrates a technique you could use in Python:
>>> import base64
>>> # payload is second of three sections in signature string
>>> payload = signature.split('.')[1]
>>> payloadlen = len(payload)
>>> # payload string may not have correct padding so add it
>>> for x in range(0, 4-(payloadlen%4)):
payload+='='
>>> print base64.decodestring(payload)
The payload from the decoded signature should contain a badge assertion (JSON with fields including uid
, recipient
, badge
etc). If the payload does not parse as a JSON object, the badge assertion should be considered invalid.
You can then use the assertion JSON for a signed badge to validate its content. At this stage you can check the badge structure and earner email address, then verify the badge assertion using the public key that should be listed in the verify.url
field.
Signed badge assertions should include the URL for the public key corresponding to the private key used for signing - in the verify.url
field. You can carry out a GET
request on the URL to retrieve the public key - if the key cannot be retrieved (i.e. it doesn't eventually return a 200 OK
status response), the badge should be considered invalid.
Once you have retrieved the public key from the verify.url
location, you can attempt to carry out verification on the JWS object. Again, your approach will be determined by your implementation language/ technology.
To verify that a badge assertion has a valid structure, your code needs to establish the following:
- the
badge
field includes a valid URL which eventually returns a200 OK
response - the
recipient
value is an object: -
type
isemail
(only value supported at the moment) -
identity
is text -
hashed
(optional) is boolean -
salt
(optional) is text -
image
(optional) is a valid URL or Data URL -
evidence
(optional) is a valid URL -
issuedOn
(optional) is a DateTime -
expires
(optional) is a DateTime -
verify
is an object: -
type
is eitherhosted
orsigned
-
url
is a valid URL
You will naturally need to carry out structural validation using a method to suit the technology or language you are building your display implementation in.
The following demonstrates checking the http status of the URL in the badge
field in Python:
>>> import requests
>>> import json
>>> # assertion has badge assertion json in it
>>> badgeclass=json.loads(assertion)['badge']
>>> print badgeclass
http://example.org/badge-class.json
>>> badgereq=requests.get(badgeclass)
>>> print badgereq.status_code
200
Once you've finished checking for structural validity, you can then check the earner email address.
A badge assertion includes information about the recipient identity in the recipient
object. The fields can include:
type
hashed
salt
identity
The type
should be email
, with the address value in the identity
field. The email address may be included as plain text, or may be hashed for additional security. If hashed, the email address may also have been salted, in which case the salt
value will be provided.
If an earner is claiming a badge using their email address, you can therefore check that against the email address stored in the assertion recipient
object. To do this in cases where the email address has been hashed, you can hash the email address of the claimed earner (with salt if appropriate) and compare it to the (salted) hash in the recipient
object.
The following demonstrates the process in Python:
>>> 'sha256$' + hashlib.sha256(claimedEmail + salt).hexdigest();
In this case claimedEmail
is the address of the earner claiming the badge and salt
is the value in the recipient.salt
field of the assertion (if provided). You can compare the output to the identity
value in the assertion - if they match, the earner identity is verified. See also How to hash and salt in various languages.
If you're dealing with a signed badge, once you've finished checking structural validity and the earner email, you can then carry out JWS verification on the assertion.
To recap, these are the verification steps for a hosted badge:
- carry out
GET
request onverify.url
to check for200
status - check badge assertion structure
- check earner email
For a signed badge:
- unpack assertion from signature payload
- check badge assertion structure
- check earner email
- retrieve public key from
verify.url
- perform JWS verification
- check revocation list
You can carry the steps out in a way that suits your display implementation.
- It is strongly recommended that a display implementation show the
verify.url
, with the origin (protocol, hostname, port if non-default) highlighted. - When checking relevant URLs for availability, the implication is that 3xx redirects are allowed, as long as the request eventually terminates on a resource that returns a
200 OK
status.
See the assertion specification for more on verification.