In the previous post, we consumed a web API which was provided freely (which we called "public") in that it didn't need to know who we were. Most web APIs require consumers to identify themselves, and do this by giving out an API key and/or client secret.
The identifiers we will obtain from an API provider are intended for our use only, and we are responsible for that usage. In this post, we'll show a couple of ways to keep them secret while we consume such APIs.
Obtaining API secrets
The process will be different for every website which provides an API. In this post, we'll use the example of The Movie Database.
As is generally the case, you will need to sign up for an account. Then, at the bottom of their home page you'll see something like this:
Notice that it greets you by Username, and has a link to their API Overview page. On that page, you can find a great deal of information, including
- how to apply for an API key
- attribution requirements
- legal requirements
- a link to language-specific wrappers (none for KRL as of this writing)
among many other kinds of information.
Be sure to get your API key by following the instructions in that section of the overview page.
Using the API key in your ruleset
You will want to find a way to get the API key into the hands of the ruleset, yet without revealing it to others. For that reason, it is not a good idea to simply hard-code it into your ruleset.
We recommend either using an entity variable to hold the API key, or to use a ruleset configuration item.
Using an entity variable to hold the API key
Have a rule in your ruleset which accepts the API key as an attribute and stores it in an entity variable. In this example code, the saveAPIkey rule selects on an incoming my_movies_app:new_settings event which has an attribute named api_key:
rule saveAPIkey {
select when my_movies_app new_settings
api_key re#(.+)# setting(api_key)
fired {
ent:api_key := api_key
raise my_movies_app event "new_api_key" attributes event:attrs
}
}
When the rule evaluates, it fires and simply stores the API key in an entity variable, ent:api_key which can be used from that moment on anywhere within the ruleset.
The rule also raises an event within the pico's bus to signal that a new API key has been stored. Another rule in the ruleset might then preload the genres from TMDb:
rule getMovieGenres {
select when my_movies_app new_api_key
pre {
the_url = "https://api.themoviedb.org/3/genre/movie/list?api_key="
response = http:get(the_url+ent:api_key)
the_genres = response.get("content").decode().get("genres")
}
fired {
ent:genres := the_genres
raise my_movies_app event "new_genres" attributes event:attrs
}
}
Now both the API key and the genres are available within the pico, as persistent entity variables.
The genres could be used in a function like this:
genres = function(){
ent:api_key.isnull() => settings() |
html:header("movie genres","")
+ <<
<h1>Movie genres</h1>
<form action="genre.html">
<select name="genre" required autofocus>
<option value="">choose a genre</option>
#{ent:genres.map(function(g){
<< <option value="#{g.get("id")}">#{g.get("name")}</option>
>>}).join("")}
</select>
<button type="submit">See movies</button>
</form>
<p>This product uses the TMDb API but is not endorsed or certified
by <a href="https://www.themoviedb.org/">TMDb</a>.</p>
>>
+ html:footer()
}
The genres function will need to be shared in the meta block of the ruleset. When invoked, the page it displays would look like this:
Notice that it is invoked with a URL that includes an event channel identifier (ECI) which is cl88l8aiy03oz9bylfyiw945k, and also names the ruleset and the function, followed by a mime type indicator of dot html. The ECI authorizes the use of the pico for this purpose, while the API key authorizes the use of the TMDb API.
The latter is kept secret by never committing it, but rather requesting it from the user of the ruleset. The ECI is only valid for a pico running on our local machine, and so it can be published freely, as it would mean nothing to any other pico.
Complete ruleset
The complete ruleset which implements this technique can be found here.
Using ruleset configuration to specify the API key
With this method, you would configure the ruleset as it is being installed, by supplying a map like the following:
And then instead of referring to an entity variable as we did above, you would use ctx:rid_config{"api_key"} to refer to the API key.
Complete ruleset
The complete ruleset which implements this technique can be found here.
No comments:
Post a Comment