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