5 title/'Security Drivers'
11A security driver is a file in `/=home=/sec/<tld>/<domain>/hoon`
12that handles the authentication for all HTTP requests to
13`https://<domain>.<tld>`. When anything in Urbit makes an HTTP
14request through `%eyre` (our web server), it checks to see if we
15have a security driver for the requested domain, and if so
16filters the request through the driver. The security driver will
17usually either decorate the request with the needed credentials
18if it has them, or else it will guide the user through the
19process of authenticating Urbit with the service.
21Each web service needs its own security driver, but most of them
22are pretty standard. We recommend starting with an existing
23security driver based on your needed authentication method (e.g.
24basic auth, OAuth1, OAuth2), and changing whatever is specific to
27The best strategy for building security drivers is to copy a
28similar one and tweak it until it works for you. The best
29representatives are Github for Basic Authentication, Twitter for
30OAuth1, Slack for OAuth2 when access tokens don't expire, and
31Google APIs for OAuth2 when access tokens do expire. In most
32cases you'll need to make very few changes to one of these
35Still, it's worth seeing one or two built from the ground up.
36Here, we'll build a connector for the Github API v3. The
37simplest way to interact with the Github API is to just fetch
38https://api.github.com from the dojo:
41 ~your-urbit:dojo> +https://api.github.com
44Github exposes a few endpoints to the general web, and the root
45endpoint is one of them. This gives you a textual representation
46of a JSON object that contains a bunch of urls to other parts of
47Github's API. This is exactly the same response you would get
48from just running `curl https://api.github.com` on UNIX.
50If we don't have a security driver for Github yet, many of the
51endpoints won't be accessible, or they will only have publicly
52accessible information. Most of what we care about requires us
57Here's a simple security driver:
60:: Test url +https://api.github.com/user
62:::: /hoon/github/com/sec
66|_ [bal=(bale keys:basic-auth) $~]
67++ aut ~(standard basic-auth bal ~)
68++ filter-request out-adding-header:aut
72Github supports authentication through either Basic
73Authentication or OAuth2. We'll show the basic auth example
74first, but in general we'd prefer OAuth2.
76Since this driver is for Github, put it in
77`/=home=/sec/com/github/hoon`.
79To try this out, we first have to initialize our credentials with
80`|init-auth-basic`, which prompts for the url of the service
81(api.github.com), username, and password. It stores this
82information in a manner that `%eyre` knows how to decode and send
83put into your `bale`. Note that `|init-auth-basic` is standard for
84basic auth, but other auth schemes (like OAuth) are initialized
87After you've run `|init-auth-basic`, you should be able to run
88`+https://api.github.com/user` and get a response indicating who
91You can similarly POST:
94 ~your-urbit:dojo> +https://api.github.com/gists &json _(cork poja need) '{"files":{"file1.txt":{"content":"can\'t stop the signal"}}}'
97This creates a gist, go to `https://gist.github.com/<username>` to
100The `+url` syntax used in "source" position makes a GET request,
101but if you pass it data it will make a POST request with that
102data as the body. This request is authenticated seamlessly. You
103can send data of any mark as long as it has a conversion path to
106Let's take a look at the code and see if we can figure out what's
113First, we load a library called `basic-auth`. This library
114exposes two items. `keys` is the type of the basic auth key,
115which is essentially a base64'd username and password.
116`standard` accepts a `bale` and produces a core with everything
117we'll need to implement basic auth.
119Every security driver needs some amount of state, which is stored
120in `bale`. `bale` contains:
122- `our`, our urbit name
123- `now`, the current time
124- `eny`, 256 bits of entropy
125- `byk`, the urbit, desk and case which the security driver is running from
126- `usr`, the particular identity we're logging in with
127- `dom`, the site we're accessing
128- `key`, the secrets required to authenticate requests. The type for this entry is supplied by the programmer as the argument to `++bale`. Thus, in our case, `key` in our bale is of type `keys:basic-auth`.
130Additionally, a security driver contains at least one out of the
133- `++filter-request` is a function which takes a hiss (http request) and produces a `sec-move` (defined in zuse), which is one of:
136 * `[%send hiss]`, which sends the new hiss. This is the
137 case in, for example, basic auth, where all we need to do is
138 add an extra header to the request.
139 * `[%give httr]`, where httr is an http response. This
140 immediately returns an http response to the sender.
141 * `[%show purl]`, where a purl is a parsed url. This displays
142 a message asking the user to visit the given url to continue
143 the authentication process.
144 * `[%redo ~]`, which redoes the request.
146 * `%eyre` calls `++filter-request` just before sending an HTTP
147 request to the specified domain. This allows the security
148 driver to filter the requests, decorating them with
152- `++filter-response` is a function which takes an httr and produces a `sec-move`. `%eyre` calls it after receiving an HTTP response from the specified domain. This allows the security driver to handle authentication errors, commonly caused by expired tokens, and retry the request.
154- `++receive-auth-query-string` is a function that takes a `quay` (list of query parameters) and produces a `sec-move`. `%eyre` calls it when it receives a request on the callback url. Generally, this happens in OAuth after the user has granted access to the urbit app, and the service makes a request to the callback url with the code. The security driver should then make a request to convert the code into an access token.
156- `++receive-auth-response` is a function that takes an httr and produces a `sec-move`. `%eyre` calls it when it gets a response to the request made in `++receive-auth-query-string`.
158- `++update` is a function which converts old state to a new format when the security driver gets updated. If you want to just get rid of the old state, define `++discard-state` as `~`.
160For basic auth, we only have to worry about `++filter-request`,
161since all we need to do is add the correct header to each
162request. Our `++aut` core contains a function
163`++out-addding-header`, which does exactly what we want.
165Where do the keys come from, though? Remember we ran
166`|init-auth-basic` to input them. This just prompts you for the
167service url, username, and password, and it stores them next to
168the driver. In our case, that's
169`/=home=/sec/com/gitub/api/atom`. These keys are stored
170encrypted, and you don't want to edit them directly. `%eyre`
171loads them directly into your `bale`.
175Most services are better accessed through some form of OAuth.
176Github can be accessed with OAuth2, which is a little more
177complicated than basic auth, but not hugely so.
179The basic steps for OAuth2 are these:
181- create an app and get its client id and client secret (on Github)
182- store these in Urbit (with `|init-oauth2`)
183- try to make a request that requires authentication. The security driver should prompt you to visit a particular url.
184- visit the url and click "authorize", which gives the app access to your account
185- the security driver will turn the code it receives into an access token
186- on every request, include that access token
188Creating an app is easy and
189[well-documented](https://github.com/settings/applications/new).
190If necessary, set the callback url to
191`/~/ac/github.com/_state/in`, and note its client id and client
194Run `|init-oauth2`, which will prompt for the hostname
195(api.github.com), client id, and client secret. This will store
196the information, encrypted, in `/=home=/sec/com/github.atom`,
197just as with Basic Authentication.
199Now we need a security driver. Use this:
202:: Test url +https://api.github.com/user
204:::: /hoon/github/com/sec
210|_ [bal=(bale keys:oauth2) tok=token:oauth2]
211++ scopes :: comment out scopes to taste
212 :~ 'user' 'user:email' 'user:follow' 'public_repo' 'repo'
213 'repo_deployment' 'repo:status' 'delete_repo' 'notifications'
214 'gist' 'read:repo_hook' 'write:repo_hook' 'admin:repo_hook'
215 'admin:org_hook' 'read:org' 'write:org' 'admin:org'
216 'read:public_key' 'write:public_key' 'admin:public_key'
218:: ++aut is a "standard oauth2" core, which implements the
219:: most common handling of oauth2 semantics. see lib/oauth2 for more details,
220:: and examples at the bottom of the file.
221++ aut (~(standard oauth2 bal tok) . |=(tok/token:oauth2 +>(tok tok)))
223 %^ out-add-query-param:aut 'access_token'
224 scope=~[%client %admin]
225 oauth-dialog='https://github.com/login/oauth/authorize'
227++ receive-auth-query-string
228 %- in-code-to-token:aut
229 url='https://github.com/login/oauth/api/access_token'
230++ receive-auth-response bak-save-token:aut
234The oauth2 library provides the main "engine" in `standard`, just
235like in basic auth, except that we also have to specify a
236function to save the access token when we get it. It's worth
237noting that this library is well documented in the source,
238including examples: `/=home=/lib/oauth2/hoon`.
240Running `+http://api.github.com/user` tries to make a request to
241Github with authentication. This loads into the bale the keys,
242which are of type `keys:oauth2`, defined in the oauth2 library.
243We don't have to worry about the specifics of these keys -- the
244library handles them -- but they include the client id and the
247Before the request is sent, `%eyre` calls `++filter-request` to
248decorate it with authentication headers. Since we don't yet have
249an access token, we need to prompt the user to visit the "dialog"
250url. `++out-add-query-param` checks whether we have a token, and
251if we don't it produces `%show` and a message telling the user to
252visit the `oauth-dialog` url we provide, with the extra query
253parameters added (client id, scopes, and state).
255When the user visits that url, Github will ask them to log in and
256authorize the application. When they do so, Github will post a
257request to `/~/ac/github.com/_state/in` with a code in the query
258string. `%eyre` sends the query string to
259`++receive-auth-query-string`, which is a function that takes a
260quay and produces a `sec-move`. `++in-code-to-token` from our
261`aut` takes the `code` parameter from the quay and uses it to
262create a request to the given url. This request includes the
263client id, the client secret, and the code we just got.
265Github's response to that request includes an access token in its
266body. This is handled in `++receive-auth-response`.
267`++bak-save-token` extracts that token and gives it to the
268function that we passed in the definition of `++aut`. In our
269case, we just save that token into our state for later usage.
271Now we have the credentials we need, so `++filter-request` is
272called with the original request, and `++out-add-query-param`
273passes the request through with the addition of the query
274parameter `access_token`.
276Just like with Basic Authentication, you should be able to run
277`+https://api.github.com/user` and get a response indicating who
280Some drivers a slightly more complicated. For example, Github's
281access token's don't expire, but that's not the case for all
282service. The googleapis driver shows the flow when the access
283token can expire. Twitter uses OAuth1, for which we have another
286Remember, of course, that the best strategy for building security
287drivers is to copy a similar one and tweak it to taste.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list