on c19bc7a help sign in
urbit.org/docs/tutorials/gall/security-driver.udon
281 lines 13 kB history raw
1:- :~ navhome/'/docs/'
2 navuptwo/'true'
3 next/'true'
4 sort/'8'
5 title/'Security Drivers'
6 ==
7;>
9# 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
25your service.
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
33models.
35Still, it's worth seeing one or two built from the ground up.
36Here, we'll build a connector for the Github API v3. (It's already in `/sec/com/github.hoon` if you want to see it.)
38The
39simplest way to interact with the Github API is to just fetch
40https://api.github.com from the dojo. First, run this:
42```
43> |init-auth-basic /com/github
44```
46Input your Github username and password as directed. Next:
48```
49~your-urbit:dojo> +https://api.github.com
50```
52> Note: the current version of this API is outdated, so you'll get a 403 message at this point. We need to update this so you can follow along with the examples. We're sorry!
54Github exposes a few endpoints to the general web, and the root
55endpoint is one of them. This gives you a textual representation
56of a JSON object that contains a bunch of urls to other parts of
57Github's API. This is exactly the same response you would get
58from just running `curl https://api.github.com` on UNIX.
60If we don't have a security driver for Github yet, many of the
61endpoints won't be accessible, or they will only have publicly
62accessible information. Most of what we care about requires us
63to be authenticated.
65## Basic auth
67Here's a simple security driver:
69```
70:: Test url +https://api.github.com/user
71::
72:::: /hoon/github/com/sec
73 ::
74/+ basic-auth
75!:
76|_ [bal=(bale keys:basic-auth) $~]
77++ aut ~(standard basic-auth bal ~)
78++ filter-request out-adding-header:aut
79--
80```
82Github supports authentication through either Basic
83Authentication or OAuth2. We'll show the basic auth example
84first, but in general we'd prefer OAuth2.
86Since this driver is for Github, put it in
87`/=home=/sec/com/github/hoon`.
89To try this out, we first have to initialize our credentials with
90`|init-auth-basic`, which prompts for the url of the service
91(api.github.com), username, and password. It stores this
92information in a manner that `%eyre` knows how to decode and send
93put into your `bale`. Note that `|init-auth-basic` is standard for
94basic auth, but other auth schemes (like OAuth) are initialized
95in other ways.
97After you've run `|init-auth-basic`, you should be able to run
98`+https://api.github.com/user` and get a response indicating who
99you're logged in as.
101You can similarly POST:
103```
104~your-urbit:dojo> +https://api.github.com/gists &json _(cork de-json:html need) '{"files":{"file1.txt":{"content":"can\'t stop the signal"}}}'
105```
107This creates a gist, go to `https://gist.github.com/<username>` to
108see it.
110The `+url` syntax used in "source" position makes a GET request,
111but if you pass it data it will make a POST request with that
112data as the body. This request is authenticated seamlessly. You
113can send data of any mark as long as it has a conversion path to
114`%mime`.
116Let's take a look at the code and see if we can figure out what's
117happening here:
119```
120/+ basic-auth
121```
123First, we load a library called `basic-auth`. This library
124exposes two items. `keys` is the type of the basic auth key,
125which is essentially a base64'd username and password.
126`standard` accepts a `bale` and produces a core with everything
127we'll need to implement basic auth.
129Every security driver needs some amount of state, which is stored
130in `bale`. `bale` contains:
132- `our`, our urbit name
133- `now`, the current time
134- `eny`, 256 bits of entropy
135- `byk`, the urbit, desk and case which the security driver is running from
136- `usr`, the particular identity we're logging in with
137- `dom`, the site we're accessing
138- `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`.
140Additionally, a security driver contains at least one out of the
141five "special" arms:
143- `++filter-request` is a function which takes a hiss (http request) and produces a `sec-move` (defined in zuse), which is one of:
144 - `[%send hiss]`, which sends the new hiss. This is the case in, for example, basic auth, where all we need to do is add an extra header to the request.
145 - `[%give httr]`, where httr is an http response. This immediately returns an http response to the sender.
146 - `[%show purl]`, where a purl is a parsed url. This displays a message asking the user to visit the given url to continue the authentication process.
147 - `[%redo ~]`, which redoes the request.
148 - `%eyre` calls `++filter-request` just before sending an HTTP request to the specified domain. This allows the security driver to filter the requests, decorating them with authentication data.
149- `++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.
150- `++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.
151- `++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`.
152- `++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 `~`.
154For basic auth, we only have to worry about `++filter-request`,
155since all we need to do is add the correct header to each
156request. Our `++aut` core contains a function
157`++out-addding-header`, which does exactly what we want.
159Where do the keys come from, though? Remember we ran
160`|init-auth-basic` to input them. This just prompts you for the
161service url, username, and password, and it stores them next to
162the driver. In our case, that's
163`/=home=/sec/com/gitub/api/atom`. These keys are stored
164encrypted, and you don't want to edit them directly. `%eyre`
165loads them directly into your `bale`.
167## OAuth2
169Most services are better accessed through some form of OAuth.
170Github can be accessed with OAuth2, which is a little more
171complicated than basic auth, but not hugely so.
173The basic steps for OAuth2 are these:
175- create an app and get its client id and client secret (on Github)
176- store these in Urbit (with `|init-oauth2`)
177- try to make a request that requires authentication. The security driver should prompt you to visit a particular url.
178- visit the url and click "authorize", which gives the app access to your account
179- the security driver will turn the code it receives into an access token
180- on every request, include that access token
182Creating an app is easy and
183[well-documented](https://github.com/settings/applications/new).
184If necessary, set the callback url to
185`/~/ac/github.com/_state/in`, and note its client id and client
186secret.
188Run `|init-oauth2`, which will prompt for the hostname
189(api.github.com), client id, and client secret. This will store
190the information, encrypted, in `/=home=/sec/com/github.atom`,
191just as with Basic Authentication.
193Now we need a security driver. Use this:
195```
196:: Test url +https://api.github.com/user
197::
198:::: /hoon/github/com/sec
199 ::
200/+ oauth2
201!:
202::::
203 ::
204|_ [bal=(bale keys:oauth2) tok=token:oauth2]
205++ scopes :: comment out scopes to taste
206 :~ 'user' 'user:email' 'user:follow' 'public_repo' 'repo'
207 'repo_deployment' 'repo:status' 'delete_repo' 'notifications'
208 'gist' 'read:repo_hook' 'write:repo_hook' 'admin:repo_hook'
209 'admin:org_hook' 'read:org' 'write:org' 'admin:org'
210 'read:public_key' 'write:public_key' 'admin:public_key'
211 ==
212:: ++aut is a "standard oauth2" core, which implements the
213:: most common handling of oauth2 semantics. see lib/oauth2 for more details,
214:: and examples at the bottom of the file.
215++ aut (~(standard oauth2 bal tok) . |=(tok/token:oauth2 +>(tok tok)))
216++ filter-request
217 %^ out-add-query-param:aut 'access_token'
218 scope=~[%client %admin]
219 oauth-dialog='https://github.com/login/oauth/authorize'
220::
221++ receive-auth-query-string
222 %- in-code-to-token:aut
223 url='https://github.com/login/oauth/api/access_token'
224++ receive-auth-response bak-save-token:aut
225--
226```
228The oauth2 library provides the main "engine" in `standard`, just
229like in basic auth, except that we also have to specify a
230function to save the access token when we get it. It's worth
231noting that this library is well documented in the source,
232including examples: `/=home=/lib/oauth2/hoon`.
234Running `+http://api.github.com/user` tries to make a request to
235Github with authentication. This loads into the bale the keys,
236which are of type `keys:oauth2`, defined in the oauth2 library.
237We don't have to worry about the specifics of these keys -- the
238library handles them -- but they include the client id and the
239client secret.
241Before the request is sent, `%eyre` calls `++filter-request` to
242decorate it with authentication headers. Since we don't yet have
243an access token, we need to prompt the user to visit the "dialog"
244url. `++out-add-query-param` checks whether we have a token, and
245if we don't it produces `%show` and a message telling the user to
246visit the `oauth-dialog` url we provide, with the extra query
247parameters added (client id, scopes, and state).
249When the user visits that url, Github will ask them to log in and
250authorize the application. When they do so, Github will post a
251request to `/~/ac/github.com/_state/in` with a code in the query
252string. `%eyre` sends the query string to
253`++receive-auth-query-string`, which is a function that takes a
254quay and produces a `sec-move`. `++in-code-to-token` from our
255`aut` takes the `code` parameter from the quay and uses it to
256create a request to the given url. This request includes the
257client id, the client secret, and the code we just got.
259Github's response to that request includes an access token in its
260body. This is handled in `++receive-auth-response`.
261`++bak-save-token` extracts that token and gives it to the
262function that we passed in the definition of `++aut`. In our
263case, we just save that token into our state for later usage.
265Now we have the credentials we need, so `++filter-request` is
266called with the original request, and `++out-add-query-param`
267passes the request through with the addition of the query
268parameter `access_token`.
270Just like with Basic Authentication, you should be able to run
271`+https://api.github.com/user` and get a response indicating who
272you're logged in as.
274Some drivers a slightly more complicated. For example, Github's
275access token's don't expire, but that's not the case for all
276service. The googleapis driver shows the flow when the access
277token can expire. Twitter uses OAuth1, for which we have another
278library.
280Remember, of course, that the best strategy for building security
281drivers is to copy a similar one and tweak it to taste.