Designing E-Learning 3.0 in gRSShopper - 12



E-Learning 3.0 - Part 1 - Part 2 - Part 3 - Part 4 - Part 5 - Part 6 - Part 7 - Part 8 - Part 9 - Part 10 - Part 11 - Part 12 - Part 13


The Badge API and Setting Up Badges

This actually represents almost a week's worth of work. You might ask, why am I working on something obsolete, like badges, using a computer language that is even more obsolete, like Perl, to create code for an application that will never be widely used, like gRSShopper. The answer, at this end of the process, is simple: now I really understand badges, and this allows me to thing about assessments and credentials in a much more structured way. It's like anything - you have to put in the work to become an expert on something before your opinions on the subject are based in evidence and practice. Just so with badges.

TL:DR here's the badging workflow:

  1. Create a Badgr account. You need to go to the Badgr website and register with an account (that is, create a UserID and a password). Here's the link to create an account: https://badgr.io/signup
  2. Obtain an Access Token. To make a request using an API you need to create an access code and put it into the header of your request. So your first API request will be to create an access token.
  3. Create an Issuer. An 'issuer' is the person who actually creates and awards badges. You would think it's the same person who just created the access token, but no.
  4. Create a Badge. You can finally create a badge! These are actually called a 'Badgeclass' (presumably because you will send many instances of this badge to different users).
  5. Award a Badge After everything else, this turned out to be pretty easy. But it still had its quirks.
If you don't want the details of how I do all this, then just skip all the way to the bottom and read the section titled 'Plans'.


HTTP Requests

When we're trying to do something like create badges using an API, we're essentially creating a series of requests using the HTTP protocol. These requests have four major components:

- The URI - this is the address to which the request will be sent (at some time in the future we will have 'content addressable APIs', which will be addressed to dat://hash addresses, but this doesn't exist yet.

- The Header - this is a set of server-to-server commands that are normally invisible to web browsers. The headers consist of a set of name-value pairs specifying certain values (eg. server type, request type (eg., POST, PUT, GET), SSL certificates, authorization, etc.)

- The Data - this is data that accompanies the request. It is a set of name-value pairs. If you fill out a form on a web page, the form contents will be sent as data to the web server.

- The Body - this is the formatted text that constitutes, say, a web page. In APIs this is usually text formatted as JSON data.

Perl uses two separate modules to make HTTP requests:

- HTTP::Request - formats the request
For example:
         my $req = HTTP::Request->new('POST', $uri, $header, $request_json);

- LWP::UserAgent - sends the request
For example:
        my $ua = LWP::UserAgent->new;
        my $response = $ua->request($req);

Other programming languages will do all this a little bit differently, but for all languages, the structure of HTTP is the same (that's what makes it great - we all use the same protocol, no matter what programming language we're using).


The Badge Workflow

One of the tricky parts about working with badges is that the badge infrastructure has its own vocabulary and a workflow that might not be obvious. Here's a plain Englisg description of the workflow for Badgr (this might vary a bit for other providers, but wince they're all using the IMS Badge specification they won't vary by much).

You can do all this on the Badgr website, which I covered in the previous post. This is specific to doing it using an API. I'm describing it Perl below, but you can perform every step of this using the sample API interface provided by Badgr called Swagger.

1. Create a Badgr account.

You need to go to the Badgr website and register with an account (that is, create a UserID and a password). Here's the link to create an account: https://badgr.io/signup

In gRSShopper I have a way to set up and keep track of accounts like this. You can access your account data by clicking on Myself -> Social Network -> Options, as follows:

Figure 137 - Social Network Options Button
This displays a screen with all your account information. There's a section for Badgr, which looks like this:

Figure 138 - Badgr Account Information in gRSShopper
I've blanked out the password and key but you are able to see these in gRSShopper (maybe I should hide them but I find it's a lot easier to offer access and depend on the user to keep this [page to themselves).

This is brand new - I haven't even uploaded this to the current version of gRSShopper in GitHub yet. But it's relatively easy to create (I should one day fix gRSShopper to add new account types). This is the code I inserted into the admin_accounts() function in admin.cgi:

Figure 139 - Adding Badgr account to admin_accounts() in admin.cgi in gRSShopper
These values (badgr_url, badgr_password, etc) are available for use as $Site variables to be used when creating a new Badgr object.


2. Obtain an Access Token

To make a request using an API you need to create an access code and put it into the header of your request. So your first API request will be to create an access token. This can be quite complicated on other websites, but Badgr handles it very simply: you sent them a request with your account login details.

They provide an example in the form of a 'curl' command. This is a Linux command that allows you to send an HTTP request and then displays the result. Here's the example:

curl -X POST 'https://api.badgr.io/o/token' -d "username=YOUREMAIL&password=YOURPASSWORD"

We can see the elements of the request in this curl command. The -X is the request format (Post), and will go in the header. Then we have the URI to which the request will be sent. Then some data is sent, signified by -d, and contains the username and password that you signed up with. In return, you will receive a response containing the access token.

Make sure you use 'https' to send this data. This will ensure that the data is encrypted, so that people snooping on your request won't have easy access to your userid and password or to the access token you receive in response.

If you are using Swagger, you click on the 'Authorize' button (pictured below) to perform this step. This will perform the exchange of information and, if you grant it permission, and store an access token for use by the interface web page (this is a process known as OAuth2).

Figure 140 - The 'Authorize' button on the Swagger sample API Interface page

In Perl I decided to create a Perl Module that would handle all the Badgr API tasks. I did this so that I could use Badgr without having to think about all these details. Each time I want to use Badgr I call up a new instance of this module, that is, 'a new Badgr object'. Here it is:


Figure 141 - Code to create a 'Badgr' module in Perl

When I need to use Badgr I create a new instance of the Badgr object by calling the module, like this:


Figure 142 - Badge Module - Create an instance of the Badgr module


You can see that I will use the HTTPS and LWP modules mentioned earlier, as well as a JSON module to handle reading and writing JSON data. Notice also that I'm using the $Site values I defined earlier to create the Badgr object.  But the main thing to see here is that I create an access token each time I call Badgr. I could create it just once and store it, but they expire pretty quickly. I could create and store a refresh token, but I found it easier just to create a new token each time I needed one.

Here's the function used by the Badgr module to actually create the access token:


Figure 143 - Badge Module - Generate Access Token

Badgr returns a response (if successful) containing a buncg of data, including an access token and refresh token. Since I'm only using the access token, I extract this from the returned data and store it in the Badgr object.


3. Create an Issuer

An 'issuer' is the person who actually creates and awards badges. You would think it's the same person who just created the access token, but no. You have to create a whole new person inside your Badgr account to issue badges (presumably this is so that more than one person can issue badges in a single account).

You use the API to submit data about the issuer. The API will then return to you an EntityId for the issuer (because 'EntityId' is used differently all over the place I will call this the 'Issuer EntityId').


Figure 144 - Create Issuer API request for Badgr in Perl
The URI to create an issuer is: https://api.badgr.io/v2/issuers

In the documentation it says "Request Path: /v2/issuers" which can be a bit confusing. That's because there might be different instances of this service out there (it is, after all, open source code). To the URIs are composed of two parts:
- the base URL, which is https://api.badgr.io
- the request path, which is: /v2/issuers

I store the base path in the account information so gRSShopper can use any Badgr service provider, not just the official one.

The Header contains the access token. It also specifies that the content is in JSON.

The body contains the JSON request. There are two required parameters: the issuer email, and the issuer home page. In gRSShopper I simply use the Site email and base URL, so you never have to type these in. This allows me to have you create an issuer simply by clicking once on a link on the account page. Once you've done that, you can forget about this state forever (I considered actually doing this automatically to completely hide this functionality; presumably also full support in gRSShopper for Badgr would allow you to create new issuers, but this isn't an LMS so I don't need to worry about it).

If you're using Swagger, scroll down to '/v2/issuers Create a new Issuer' and then click on the 'Try it out' button (on the right hand side of the page). You'll be presented with every possible field. Delete the fields you don't want to use for now (which is most of them), then fill in values for the email and url (it will fail without these). Then click 'Execute'.

Figure 145 - Create Issuer in Swagger on Badgr

Note: If you get this error - "JSON parse error - Expecting property name enclosed in double quotes: line 6 column 1 (char 99)" - it's because of the comma after the last parameter (ie., after the URL string). Normally this is no problem for JSON but the parser used in Badgr cannot handle these trailing commas, so you have to make sure there are no extra commands in your JSON code.

The email address will have to be one of your verified addresses associated with your Badgr profile (I just use the address I used to create the profile). You may get other errors depending on your content. But if your request is successful you'll get an 'EntityId' encoded in the JSON. Copy this down or save it. You will need it for any future badge request (gRSShopper just saves it in the account information).


4. Create a Badge

You can finally create a badge! These are actually called a 'Badgeclass' (presumably because you will send many instances of this badge to different users).

According to the API documentation the only required parameters are the badge name and description, but you'll find that this doesn't work. Here is what I found (though trial and error) and the real required parameters for a badge:
- issuer
- name
- description
- image
- criteriaUrl
- alignments:
   - targetUrl
   - targetDescription

The issuer is specified by the 'issuer EntityId' you created in the previous step. Anything named URL needs to be a URL. And 'image' is a special case all in its own right (you can create a badge without creating an image but it looks really ugly and probably won't work in many applications).

The URI to create a badge is: https://api.badgr.io/v2/badgeclasses
- the base URL, which is https://api.badgr.io
- the request path, which is: /v2/badgeclasses

The Header contains the access token. It also specifies that the content is in JSON.

Here's how I set up the JSON to create a badge:

Figure 146 - JSON for Create Badge in JSON in gRSShopper
As noted above, the image is a special case. Here's what the data model specifies:"image (string) ($data:image/png;base64) Base64 encoded string of an image that represents the BadgeClass." Also, only PNG or SVG images can be accepted.

Base64 is a limited form of encoding. It's used because regular image encoding contains characters that can't be send in a JSON request. I would imagine it is also required because of the need to process the image when the badge is awarded (the data is 'baked' into the image). Otherwise, you could just use any old image URL. But that's not an option here.

I found a website that will convert my images to Basse64 so I would have something to test with. Here it is: https://www.base64-image.de/

You can save the image encoding and place it right into the JSON as a string. Like this:

"image":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATUAAAEhCAYAAAAeW
DeJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAJOgAACToAYJj
BRwAAP+lSURBVHhe7P2HdxVXti2M3//iG79vvPHeHbfDaLdTGzdtjLEiypFgYxtjGzA5Z2Ocwc
YmZwRKBJGRUM45Syjno5xzznl+c+06RwghA+1298/v9i2YqnDq1Knaq9bcc+34H/iXLJPq39TCzckJba02JscVHh2bDvkjH0xwV8D9xz7/GajvyHW5I//lZwwYI/iRdt7MRf898KQpzHrif59FHs8ALpIChlQQGPYlKQ3bkiqjai1WEbvQfkzYcSbsuDqiP3
N6wk87NjlOS06HfKS/viH1J+RU/sjEiAbZVtDbTn+1/1kMyzQbPlrkgCFFxWKDRC/RRwwQQ4RmSYOvaNeY+b1hQr7bT8h35RryXflcoDeK9uVHm/rdf+XyLyE1edknJoe55oPrH1Rt6jGhX4/rIS/zBBN4AiMYm+xnknUzybq4bmfydmFkso/oxfBkj1qP8JxRdd7AFMaJCQX+Lr8lvz0p15z2e2LECe6o+5pyEdk2GGq6sf4bL4aXT+zClaTCGFm
GKaO25V2XDyaYHFPpxw/GSUajNNg4d0YmR4hhjE0M8xjtM9KNseEuTAz3YGKI9hvsxP
hQp9ofH+3lOf0YmaDNJocwP
qEHrzFGjPIft3iM1DhGwhOM8l74+/K7cj8GS8k9="

(Don't actually use this example - I trimmed it to make it readable here, and it won't actually work. If you ant to try this is Swagger you'll have to encode your own image).

For gRSShopper I found a way to encode images into Base64 using a Perl module. There were some quirks, with the result that it took a full day to figure this all out (I can be really slow sometimes).

Figure 147 - Encoding an image into Base64 in Perl for gRSShopper
You can see in the code what I had to do. First, control characters (such as line feed (\n) or carriage return (\r) were being embedded into the file string, so I had to remove them (the only place this mattered was in Badgr - they would display just fine in a browser - I actually had to do a character-by-character analysis of the image files to find this problem). Second, I had to insert the "data:image/png;base64," string into the encoded output to make it recognizable as an image to both browsers and to Badgr because Perl doesn't do that (found this by trial and error)).

This could have been a lot easier but it wasn't.

For gRSShopper, I set up a new type of data element, called a Badge, with some basic data items. here's what the Badge editor looks like in the Form editor:

Figure 148 - The Badge editor as defined in the Form Editor


Here's the new Badge editor in gRSShopper (made the same way I made the Task editor, etc).

Figure 149 - Badge Editor in gRSShopper

Using gRSShopper, the first step is to create the Badge itself. You give it a name and a description, then associate it with a Task (the idea is that you get the badge for performing the task). You then upload a PNG image for the badge in the upload tab (this works exactly the same as 'Upload' for posts; I added a line of code to support Upload for badges).

When you're ready, you Publish the badge. All you need to do is click on the Publish button in the Publish tab. gRSShopper will then convert the image to Base64, create the API request to Badgr, send the request, and store the important data to the Badge record (and specifically, the Badge entityId, which you need to use in order to award this badge later.

Note that when the badge is created by gRSShopper, both the information from the Badge and the information from the associated Task is sent.


5. Award a Badge

After everything else, this turned out to be pretty easy. But it still had its quirks.

First of all, the word we use when we mean "award a badge" is "assertion". Why 'assertion' is a better word to use here than 'award' or 'grant' or something normal, I don't know.

Second, just to change things up, we create our URI in a new way.

https://api.badgr.io/v2/badgeclasses/m1-bQL4ISHeL_oPGlKgDYw/assertions
- the base URL, which is https://api.badgr.io
- the request path, which is: /v2/badgeclasses/m1-bQL4ISHeL_oPGlKgDYw/assertions
where m1-bQL4ISHeL_oPGlKgDYw is the badge entityId for the badge we just created (it's stored in gRSShopper as badge_badgrid).

That's right. The badge entityId is embedded in the URI. (Note - though the Guide says "Request Path: /v2/badgeclasses/:badgeclass_entity_id/assertions" do not put a colen into the URI).

The Header contains the access token. It also specifies that the content is in JSON.

In the body, we specify information about the assertion. The only required element is the identity of the recipient. But a good assertion will also contain information about the evidence used to make the assertion.

The recipient may be identified one of three ways: by an email address, by a phone number, or by a URL. You can only use one of them, and you specify which type of identity you used in a separate parameter.

If you want, you can hash the identity of the recipient. If you do, set the value of the 'hash' parameter to true. Then, in the 'plaintextIdentity' you would put the plain text version of the recipient's identity (so, essentially, this creates a mechanism for the issuer to digitally sign the assertion).

So, here's the recipient data element with some sample values:


  "recipient": {
    "identity": "stephen@downes.ca",
    "type": "email",
    "hashed": false,
    "plaintextIdentity": "stephen@downes.ca"
  },

The 'evidence' is a set of one or more values, where each value consists of a 'url' and a 'narrative' (think of each one of these being a separate task that you would have to do to earn a badge). It's encoded like this:


  "evidence": [
    {
      "url": "string",
      "narrative": "string"
    }
  ],

The evidence URL is the URL for the blog post or web site the recipient created in order to prove that they completed the work. The narrative is pretty free-form; I use it to store the URL of the task associated with the evidence.

There is also an undocumented 'notify' parameter in the API. If you set it to 'true', and the recipient type is 'email', then Badgr will send an email to the badge recipient.

Put these all together and you get your request JSON:


  {
    "recipient": {
      "identity": "$recipient_email",
      "type": "email",
      "hashed": false,
      "plaintextIdentity": ""
    },
    "notify":true,
    "evidence":[
      {
        "url":"https://www.downes.ca/post/2",
        "narrative":"https://el30.mooc.ca/task/8"
      }
    ]
  }



So, that's where I am today - I took the day to do the last section (Award Badge) and then to write this article.

Plans

I have the following immediate short-term plans:

- first, to create a simple form where you can submit your blog post for consideration for a badge.

- second, to create an 'Award Badge' button for harvested posts as viewed in the RSS Reader, so all I need to do to award a badge is to press the button.

- third, to create a WebReference notification  mechanism, so that whether or not I'm using Badgr, I can award a badge and send a notrification.

- fourth, when I award a badge, I will add it to the blockchain for this site. So the course website will have a record of all the people who received a badge (note that I could digitally sign each record, and hide the real identity of the recipient, which would give people the choice of whether or not they want to be recognized as having actieved the badge).

I don't have any immediate plans for the backpack because I don't really see that as valuable. Maybe I'm wrong about this.

My longer-term plans include something like the following:

- automatic badge-awarding: if your content is being harvested, then if it contains the course hashtag and a task hashtag, then the appropriate badge is automatically awarded and the notification sent.

- evaluative mechanism: a framework where I could use an AI tool to actually evaluate the blog post to determine whether the criteria were satisfied (actually building this I'll leave to the AI people).

As Jenny said earlier, Badging is in no way sufficient, not even if it's embedded into a MOOC system like gRSShopper. But now we have the mechanism and the vocabulary to actually begin considering some of the questions about recognition.

For example, in the realm if criteria, there's a whole discussion to have about competencies. How do we define competencies? How do we embed them into tasks? How do we recognize when a competency has been satisfied? Are competencies even the right thing to be using as the basis for assessment and recognition?

And in the realm of assessment: how do we assess? Do we use AI? How do we train the AI to recognize successful behaviour (and how do we teach it to ignore factors that are irrelevant to competency, like gender or race?)

And there are questions about privacy. Should we conduct assessments even in cases where a person didn't ask to be assessed? Should we keep the results of these assessments private? How do we verify the credentials? How do we validate courses, or issuers?








Comments

Popular Posts