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