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