on master help sign in
Add basic authentication to tutorial
c22d74caaron levin
doc/tutorial/ApiType.lhs+47
@@ -309,3 +309,50 @@ One example for this is if you want to serve a directory of static files along
309with the rest of your API. But you can plug in everything that is an
309with the rest of your API. But you can plug in everything that is an
310`Application`, e.g. a whole web application written in any of the web
310`Application`, e.g. a whole web application written in any of the web
311frameworks that support `wai`.
311frameworks that support `wai`.
312+
313+### Basic Authentication
314+
315+Once you've established the basic routes and semantics of your API, it's time to consider protecting parts of it. Authentication and authorization are broad and nuanced topics; as servant began to explore this space we started small with one of HTTP's earliest authentication schemes: [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
316+
317+When protecting endpoints with basic authentication, we need to specify two items:
318+
319+1. The **realm** of authentication as per the Basic Authentictaion spec.
320+2. The datatype returned by the server after authentication is verified. This is usually a `User` or `Customer` type datatype.
321+
322+With those two items in mind, *servant* provides the following combinator:
323+
324+``` haskell ignore
325+data BasicAuth (realm :: Symbol) (userData :: *)
326+```
327+
328+You can use this combinator to protect an API as follows:
329+
330+``` haskell
331+-- | Simple data type for our weather api
332+data WeatherData =
333+ WeatherData { temp :: Double
334+ , wind :: Int
335+ } deriving (Eq, FromJSON, Generic, Ord, ToJSON)
336+
337+-- | The user data returned after basic authentication
338+data User =
339+ User { username :: String
340+ , city :: String
341+ , state :: String
342+ , country :: String
343+ } deriving (Eq, FromJSON, Generic, Ord, ToJSON)
344+
345+-- | parts of the API open to the public (no authentication required)
346+type PublicAPI12 = "public" :> "weather" :> Get '[JSON] WeatherData
347+
348+-- | parts of the API protected by basic authentication
349+type PrivatePAI12 = "private" :> "weather"
350+ :> Capture "city" String
351+ :> ReqBody '[JSON] WeatherData
352+ :> Post '[JSON] ()
353+ :<|> "private" :> "account"
354+ :> Get '[PlainText] String
355+
356+-- | Our full Weather API, private API protected by basic authentication.
357+type ProtectedAPI12 = PublicAPI12
358+ :<|> BasicAuth "weather" User :> PrivateAPI12
doc/tutorial/Authentication.lhsnew file+417
@@ -0,0 +1,417 @@
1+# Authentication in Servant
2+
3+Once you've established the basic routes and semantics of your API, it's time
4+to consider protecting parts of it. Authentication and authorization are broad
5+and nuanced topics; as servant began to explore this space we started small
6+with one of HTTP's earliest authentication schemes: [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
7+
8+Servant `0.5` shipped with out-of-the-box support for Basic Authentication.
9+However, we recognize that every web application is its own beautiful snowflake
10+and are offering experimental support for generalized or ad-hoc authentication.
11+
12+In this tutorial we'll build two APIs. One protecting certain routes with Basic
13+Authentication and another protecting the same routes with a custom, in-house
14+authentication scheme.
15+
16+## Basic Authentication
17+
18+When protecting endpoints with basic authentication, we need to specify two
19+items:
20+
21+1. The **realm** of authentication as per the Basic Authentication spec.
22+2. The datatype returned by the server after authentication is verified. This
23+is usually a `User` or `Customer` datatype.
24+
25+With those two items in mind, *servant* provides the following combinator:
26+
27+``` haskell ignore
28+data BasicAuth (realm :: Symbol) (userData :: *)
29+```
30+
31+You can use this combinator to protect an API as follows:
32+
33+```haskell
34+{-# LANGUAGE DataKinds #-}
35+{-# LANGUAGE DeriveGeneric #-}
36+{-# LANGUAGE FlexibleContexts #-}
37+{-# LANGUAGE FlexibleInstances #-}
38+{-# LANGUAGE MultiParamTypeClasses #-}
39+{-# LANGUAGE OverloadedStrings #-}
40+{-# LANGUAGE ScopedTypeVariables #-}
41+{-# LANGUAGE TypeFamilies #-}
42+{-# LANGUAGE TypeOperators #-}
43+{-# LANGUAGE UndecidableInstances #-}
44+
45+module Authentication where
46+
47+import Data.Aeson (ToJSON)
48+import Data.Proxy (Proxy (Proxy))
49+import Data.Text (Text)
50+import GHC.Generics (Generic)
51+import Network.Wai.Handler.Warp (run)
52+import Servant.API ((:<|>) ((:<|>)), (:>), BasicAuth,
53+ Get, JSON)
54+import Servant.API.BasicAuth (BasicAuthData (BasicAuthData))
55+import Servant.API.Experimental (AuthProtect)
56+import Servant.Server (BasicAuthCheck (BasicAuthCheck),
57+ BasicAuthResult( Authorized
58+ , Unauthorized
59+ ),
60+ Context ((:.), EmptyContext), Server,
61+ serveWithContext)
62+import Servant.Server.Experimental.Auth (AuthHandler, AuthServerData,
63+ mkAuthHandler)
64+import Servant.Server.Experimenta.Auth()
65+
66+- | private data that needs protection
67+newtype PrivateData = PrivateData { ssshhh :: Text }
68+ deriving (Eq, Show, Generic)
69+
70+instance ToJSON PrivateData
71+
72+-- | public data that anyone can use.
73+newtype PublicData = PublicData { somedata :: Text }
74+ deriving (Eq, Show, Generic)
75+
76+instance ToJSON PublicData
77+
78+-- | A user we'll grab from the database when we authenticate someone
79+newtype User = User { userName :: Text }
80+ deriving (Eq, Show)
81+
82+-- | a type to wrap our public api
83+type PublicAPI = Get '[JSON] [PublicData]
84+
85+-- | a type to wrap our private api
86+type PrivateAPI = Get '[JSON] PrivateData
87+
88+-- | our API
89+type BasicAPI = "public" :> PublicAPI
90+ :<|> "private" :> BasicAuth "foo-realm" User :> PrivateAPI
91+
92+-- | a value holding a proxy of our API type
93+basicAuthApi :: Proxy BasicAPI
94+basicAuthApi = Proxy
95+```
96+
97+You can see that we've prefixed our public API with "public" and our private
98+API with "private." Additionally, the private parts of our API use the
99+`BasicAuth` combinator to protect them under a Basic Authentication scheme (the
100+realm for this authentication is `"foo-realm"`).
101+
102+Unfortunately we're not done. When someone makes a request to our `"private"`
103+API, we're going to need to provide to servant the logic for validifying
104+usernames and passwords. This adds a certain conceptual wrinkle in servant's
105+design that we'll briefly discuss. If you want the **TL;DR**: we supply a lookup
106+function to servant's new `Context` primitive.
107+
108+Until now, all of servant's API combinators extracted information from a request
109+or dictated the structure of a response (e.g. a `Capture` param is pulled from
110+the request path). Now consider an API resource protected by basic
111+authentication. Once the required `WWW-Authenticate` header is checked, we need
112+to verify the username and password. But how? One solution would be to force an
113+API author to provide a function of type `BasicAuthData -> ExceptT ServantErr IO User`
114+and servant should use this function to authenticate a request. Unfortunately
115+this didn't work prior to `0.5` because all of servant's machinery was
116+engineered around the idea that each combinator can extract information from
117+only the request. We cannot extract the function
118+`BasicAuthData -> ExceptT ServantErr IO User` from a request! Are we doomed?
119+
120+Servant `0.5` introduced `Context` to handle this. The type machinery is beyond
121+the scope of this tutorial, but the idea is simple: provide some data to the
122+`serve` function, and that data is propagated to the functions that handle each
123+combinator. Using `Context`, we can supply a function of type
124+`BasicAuthData -> ExceptT ServantErr IO User` to the `BasicAuth` combinator
125+handler. This will allow the handler to check authentication and return a `User`
126+to downstream handlers if successful.
127+
128+In practice we wrap `BasicAuthData -> ExceptT ServantErr IO` into a slightly
129+different function to better capture the semantics of basic authentication:
130+
131+``` haskell ignore
132+-- | The result of authentication/authorization
133+data BasicAuthResult usr
134+ = Unauthorized
135+ | BadPassword
136+ | NoSuchUser
137+ | Authorized usr
138+ deriving (Eq, Show, Read, Generic, Typeable, Functor)
139+
140+-- | Datatype wrapping a function used to check authentication.
141+newtype BasicAuthCheck usr = BasicAuthCheck
142+ { unBasicAuthCheck :: BasicAuthData
143+ -> IO (BasicAuthResult usr)
144+ }
145+ deriving (Generic, Typeable, Functor)
146+```
147+
148+We now use this datatype to supply servant with a method to authenticate
149+requests. In this simple example the only valid username and password is
150+`"servant"` and `"server"`, respectively, but in a real, production application
151+you might do some database lookup here.
152+
153+```haskell
154+-- | 'BasicAuthCheck' holds the handler we'll use to verify a username and password.
155+authCheck :: BasicAuthCheck User
156+authCheck =
157+ let check (BasicAuthData username password) =
158+ if username == "servant" && password == "server"
159+ then return (Authorized (User "servant"))
160+ else return Unauthorized
161+ in BasicAuthCheck check
162+```
163+
164+And now we create the `Context` used by servant to find `BasicAuthCheck`:
165+
166+```haskell
167+-- | We need to supply our handlers with the right Context. In this case,
168+-- Basic Authentication requires a Context Entry with the 'BasicAuthCheck' value
169+-- tagged with "foo-tag" This context is then supplied to 'server' and threaded
170+-- to the BasicAuth HasServer handlers.
171+serverContext :: Context (BasicAuthCheck User ': '[])
172+serverContext = authCheck :. EmptyContext
173+```
174+
175+We're now ready to write our `server` method that will tie everything together:
176+
177+```haskell
178+-- | an implementation of our server. Here is where we pass all the handlers to our endpoints.
179+-- In particular, for the BasicAuth protected handler, we need to supply a function
180+-- that takes 'User' as an argument.
181+server :: Server BasicAPI
182+server =
183+ let publicAPIHandler = return [PublicData "foo", PublicData "bar"]
184+ privateAPIHandler (user :: User) = return (PrivateData (userName user))
185+ in publicAPIHandler :<|> privateAPIHandler
186+```
187+
188+Finally, our main method and a sample session working with our server:
189+
190+```haskell
191+-- | hello, server!
192+basicAuthMain :: IO ()
193+basicAuthMain = run 8080 (serveWithContext basicAuthApi serverContext server)
194+
195+{- Sample session
196+
197+$ curl -XGET localhost:8080/public
198+[{"somedata":"foo"},{"somedata":"bar"}
199+
200+$ curl -iXGET localhost:8080/private
201+HTTP/1.1 401 Unauthorized
202+transfer-encoding: chunked
203+Date: Thu, 07 Jan 2016 22:36:38 GMT
204+Server: Warp/3.1.8
205+WWW-Authenticate: Basic realm="foo-realm"
206+
207+$ curl -iXGET localhost:8080/private -H "Authorization: Basic c2VydmFudDpzZXJ2ZXI="
208+HTTP/1.1 200 OK
209+transfer-encoding: chunked
210+Date: Thu, 07 Jan 2016 22:37:58 GMT
211+Server: Warp/3.1.8
212+Content-Type: application/json
213+{"ssshhh":"servant"}
214+-}
215+```
216+
217+## Generalized Authentication
218+
219+Sometimes your server's authentication scheme doesn't quite fit with the
220+standards (or perhaps servant hasn't rolled-out support for that new, fancy
221+authentication scheme). For such a scenario, servant `0.5` provides easy and
222+simple experimental support to roll your own authentication.
223+
224+Why experimental? We worked on the design for authentication for a long time. We
225+really struggled to find a nice, type-safe niche in the design space. In fact,
226+`Context` came out of this work, and while it really fit for schemes like Basic
227+and JWT, it wasn't enough to fully support something like OAuth or HMAC, which
228+have flows, roles, and other fancy ceremonies. Further, we weren't sure *how*
229+people will use auth.
230+
231+So, in typical startup fashion, we developed an MVP of 'generalized auth' and
232+released it in an experimental module, with the hope of getting feedback from you!
233+So, if you're reading this or using generalized auth support, please give us
234+your feedback!
235+
236+### What is Generalized Authentication?
237+
238+**TL;DR**: you throw a tagged `AuthProtect` combinator in front of the endpoints
239+you want protected and then supply a function `Request -> ExceptT IO ServantErr user`
240+which we run anytime a request matches a protected endpoint. It precisely solves
241+the "I just need to protect these endpoints with a function that does some
242+complicated business logic" and nothing more. Behind the scenes we use a type
243+family instance (`AuthServerData`) and `Context` to accomplish this.
244+
245+### Generalized Authentication in Action
246+
247+Let's implement a trivial authentication scheme. We will protect our API by
248+looking for a cookie named `"servant-auth-cookie"`. This cookie's value will
249+contain a key from which we can lookup a `User`.
250+
251+```haskell
252+-- | A user type that we "fetch from the database" after
253+-- performing authentication
254+newtype User = User { unUser :: Text }
255+
256+-- | A (pure) database mapping keys to users.
257+database :: Map ByteString User
258+database = fromList [ ("key1", User "Anne Briggs")
259+ , ("key2", User "Bruce Cockburn")
260+ , ("key3", User "Ghédalia Tazartès")
261+ ]
262+
263+-- | A method that, when given a password, will return a User.
264+-- This is our bespoke (and bad) authentication logic.
265+lookupUser :: ByteString -> ExceptT ServantErr IO User
266+lookupUser key = case Map.lookup key database of
267+ Nothing -> throwE (err403 { errBody = "Invalid Cookie" })
268+ Just usr -> return usr
269+```
270+
271+For generalized authentication, servant exposes the `AuthHandler` type,
272+which is used to wrap the `Request -> ExceptT IO ServantErr user` logic. Let's
273+create a value of type `AuthHandler Request User` using the above `lookupUser`
274+method:
275+
276+```haskell
277+-- | The auth handler wraps a function from Request -> ExceptT ServantErr IO User
278+-- we look for a Cookie and pass the value of the cookie to `lookupUser`.
279+authHandler :: AuthHandler Request User
280+authHandler =
281+ let handler req = case lookup "servant-auth-cookie" (requestHeaders req) of
282+ Nothing -> throwE (err401 { errBody = "Missing auth header" })
283+ Just authCookieKey -> lookupUser authCookieKey
284+ in mkAuthHandler handler
285+```
286+
287+Let's now protect our API with our new, bespoke authentication scheme. We'll
288+re-use the endpoints from our Basic Authentication example.
289+
290+```haskell
291+-- | Our API, with auth-protection
292+type AuthGenAPI = "private" :> AuthProtect "cookie-auth" :> PrivateAPI
293+ :<|> "public" :> PublicAPI
294+
295+-- | A value holding our type-level API
296+genAuthApi :: Proxy AuthGenAPI
297+genAuthApi = Proxy
298+```
299+
300+Now we need to bring everything together for the server. We have the
301+`AuthHandler Request User` value and an `AuthProtected` endpoint. To bind these
302+together, we need to provide a [Type Family](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/type-families.html)
303+instance that tells the `HasServer` instance that our `Context` will supply a
304+`User` (via `AuthHandler Request User`) and that downstream combinators will
305+have access to this `User` value (or an error will be thrown if authentication
306+fails).
307+
308+```haskell
309+
310+-- | We need to specify the data returned after authentication
311+type instance AuthServerData (AuthProtect "cookie-auth") = User
312+```
313+
314+Note that we specify the type-level tag `"cookie-auth"` when defining the type
315+family instance. This allows us to have multiple authentication schemes
316+protecting a single API.
317+
318+We now construct the `Context` for our server, allowing us to instantiate a
319+value of type `Server AuthGenAPI`, in addition to the server value:
320+
321+```haskell
322+-- | The context that will be made available to request handlers. We supply the
323+-- "cookie-auth"-tagged request handler defined above, so that the 'HasServer' instance
324+-- of 'AuthProtect' can extract the handler and run it on the request.
325+serverContext :: Context (AuthHandler Request User ': '[])
326+serverContext = authHandler :. EmptyContext
327+
328+-- | Our API, where we provide all the author-supplied handlers for each end
329+-- point. Note that 'privateDataFunc' is a function that takes 'User' as an
330+-- argument. We dont' worry about the authentication instrumentation here,
331+-- that is taken care of by supplying context
332+server :: Server AuthGenAPI
333+server =
334+ let privateDataFunc (User name) =
335+ return [PrivateData ("this is a secret: " <> name)]
336+ publicData = [PublicData "this is a public piece of data"]
337+ in privateDataFunc :<|> return publicData
338+```
339+
340+We're now ready to start our server (and provide a sample session)!
341+
342+```haskell
343+-- | run our server
344+genAuthMain :: IO ()
345+genAuthMain = run 8080 (serveWithContext api serverContext server)
346+
347+{- Sample Session:
348+
349+$ curl -XGET localhost:8080/private
350+Missing auth header
351+
352+$ curl -XGET localhost:8080/private -H "servant-auth-cookie: key3"
353+[{"ssshhh":"this is a secret: Ghédalia Tazartès"}]
354+
355+$ curl -XGET localhost:8080/private -H "servant-auth-cookie: bad-key"
356+Invalid Cookie
357+
358+$ curl -XGET localhost:8080/public
359+[{"somedata":"this is a public piece of data"}]
360+-}
361+```
362+
363+### Recap
364+
365+Creating a generalized, ad-hoc authentication scheme was fairly straight
366+forward:
367+
368+1. use the `AuthProtect` combinator to protect your API.
369+2. choose a application-specific data type used by your server when
370+authentication is successful (in our case this was `User`).
371+3. Create a value of `AuthHandler Request User` which encapsulates the
372+authentication logic (`Request -> ExceptT IO ServantErr User`). This function
373+will be executed everytime a request matches a protected route.
374+4. Provide an instance of the `AuthServerData` type family, specifying your
375+application-specific data type returned when authentication is successful (in
376+our case this was `User`).
377+
378+Caveats:
379+
380+1. The module `Servant.Server.Experimental.Auth` contains an orphan `HasServer`
381+instance for the `AuthProtect` combinator. You may be get orphan instance
382+warnings when using this.
383+2. Generalized authentication requires the `UndecidableInstances` extension.
384+
385+## Client-side Authentication
386+
387+### Basic Authentication
388+
389+As of `0.5`, *servant-client* comes with support for basic authentication!
390+Endpoints protected by Basic Authentication will require a value of type
391+`BasicAuthData` to complete the request.
392+
393+### Generalized Authentication
394+
395+Servant `0.5` also shipped with support for generalized authentication. Similar
396+to the server-side support, clients need to supply an instance of the
397+`AuthClientData` type family specifying the datatype the client will use to
398+marshal an unauthenticated request into an authenticated request. Generally,
399+this will look like:
400+
401+```haskell ignore
402+-- | The datatype we'll use to authenticate a request. If we were wrapping
403+-- something like OAuth, this might be a Bearer token.
404+type instance AuthClientData (AuthProtect "cookie-auth") = String
405+
406+-- | A method to authenticate a request
407+authenticateReq :: String -> Req -> Req
408+authenticateReq s req = SCR.addHeader "my-bespoke-header" s req
409+```
410+
411+Now, if the client method for our protected endpoint was `getProtected`, then
412+we could perform authenticated requests as follows:
413+
414+```haskell ignore
415+-- | one could curry this to make it simpler to work with.
416+result = runExceptT (getProtected (mkAuthenticateReq "secret" authenticateReq))
417+```
doc/tutorial/Server.lhs−2
@@ -1108,8 +1108,6 @@ $ curl http://localhost:8081/b
1108"hi"
1108"hi"
1109```
1109```
1110
1110
1111−## Conclusion
1112−
1113You're now equipped to write webservices/web-applications using
1111You're now equipped to write webservices/web-applications using
1114**servant**. The rest of this document focuses on **servant-client**,
1112**servant**. The rest of this document focuses on **servant-client**,
1115**servant-js** and **servant-docs**.
1113**servant-js** and **servant-docs**.