aboutsummaryrefslogtreecommitdiff
path: root/doc/chapters/basicauthentication.inc
diff options
context:
space:
mode:
Diffstat (limited to 'doc/chapters/basicauthentication.inc')
-rw-r--r--doc/chapters/basicauthentication.inc207
1 files changed, 207 insertions, 0 deletions
diff --git a/doc/chapters/basicauthentication.inc b/doc/chapters/basicauthentication.inc
new file mode 100644
index 00000000..8e7f4c60
--- /dev/null
+++ b/doc/chapters/basicauthentication.inc
@@ -0,0 +1,207 @@
1With the small exception of IP address based access control,
2requests from all connecting clients where served equally until now.
3This chapter discusses a first method of client's authentication and
4its limits.
5
6A very simple approach feasible with the means already discussed would
7be to expect the password in the @emph{URI} string before granting access to
8the secured areas. The password could be separated from the actual resource identifier
9by a certain character, thus the request line might look like
10@verbatim
11GET /picture.png?mypassword
12@end verbatim
13@noindent
14
15In a situation, where the client is customized enough and the connection occurs
16through secured lines (e.g., a embedded device directly attached to another via wire),
17this can be a reasonable choice.
18
19But when it is assumed that the user connecting does so with an ordinary Internet browser,
20this implementation brings some problems about. For example, the URI including the password
21stays in the address field or at least in the history of the browser for anybody near enough to see.
22It will also be inconvenient to add the password manually to any new URI when the browser does
23not know how to compose this automatically.
24
25At least the convenience issue can be addressed by employing the simplest built-in password
26facilities of HTTP compliant browsers, hence we want to start there. It will however turn out
27to have still severe weaknesses in terms of security which need consideration.
28
29Before we will start implementing @emph{Basic Authentication} as described in @emph{RFC 2617},
30we should finally abandon the bad practice of responding every request the first time our callback
31is called for a given connection. This is becoming more important now because the client and
32the server will have to talk in a more bi-directional way than before to
33
34But how can we tell whether the callback has been called before for the particular connection?
35Initially, the pointer this parameter references is set by @emph{MHD} in the callback. But it will
36also be "remembered" on the next call (for the same connection).
37Thus, we will generate no response until the parameter is non-null---implying the callback was
38called before at least once. We do not need to share information between different calls of the callback,
39so we can set the parameter to any adress that is assured to be not null. The pointer to the
40@code{connection} structure will be pointing to a legal adress, so we take this.
41
42Not even the headers will be looked at on the first iteration.
43
44@verbatim
45int answer_to_connection (void *cls, struct MHD_Connection *connection,
46 const char *url, const char *method, const char *version,
47 const char *upload_data, unsigned int *upload_data_size,
48 void **con_cls)
49{
50 if (0 != strcmp(method, "GET")) return MHD_NO;
51 if (NULL == *con_cls) {*con_cls = connection; return MHD_YES;}
52
53 ...
54 /* else respond accordingly */
55 ...
56}
57@end verbatim
58@noindent
59
60Note how we lop off the connection on the first condition, but return asking for more on
61the other one with @code{MHD_YES}.
62With the framework improved, we can proceed to implement the actual authentication process.
63
64@heading Request for authentication
65
66Let us assume we had only files not intended to be handed out without the correct username/password,
67so every "GET" request will be challenged.
68@emph{RFC 2617} describes how the server shall ask for authentication by adding a
69@emph{WWW-Authenticate} response header with the name of the @emph{realm} protected.
70
71We let an extra function function do this.
72@verbatim
73int ask_for_authentication (struct MHD_Connection *connection, const char *realm)
74{
75 int ret;
76 struct MHD_Response *response;
77 char *headervalue;
78 const char *strbase = "Basic realm=";
79
80 response = MHD_create_response_from_data (0, NULL, MHD_NO, MHD_NO);
81 if (!response) return MHD_NO;
82
83 headervalue = malloc (strlen (strbase) + strlen (realm) + 1);
84 if (!headervalue) return MHD_NO;
85
86 strcpy (headervalue, strbase);
87 strcat (headervalue, realm);
88
89 ret = MHD_add_response_header (response, "WWW-Authenticate", headervalue);
90 free (headervalue);
91 if (!ret) {MHD_destroy_response (response); return MHD_NO;}
92
93 ret = MHD_queue_response (connection, MHD_HTTP_UNAUTHORIZED, response);
94
95 MHD_destroy_response (response);
96
97 return ret;
98}
99@end verbatim
100@noindent
101
102@code{#define} the realm name according to your own taste, e.g. "Maintenance" or "Area51" but
103it will need to have extra quotes.
104
105But the client may send the authentication right away, so it would be wrong to ask for
106it without checking the request's header--where the authentication is expected to be found.
107
108@heading Authentication in detail
109Checking @emph{RFC 2617} again, we find that the client will pack the username and password, by
110whatever means he might have obtained them, in a line separated by a colon---and then encodes
111them to @emph{Base64}. The actual implementation of this encoding are not within the scope of
112this tutorial although a working function is included in the complete source file of the example.
113
114An unencoded word describing the authentication method (here "Basic") will precede the code
115and the resulting line is the value of a request header of the type "Authorization".
116
117This header line thus is of interest to the function checking a connection for a given username/password:
118@verbatim
119int is_authenticated (struct MHD_Connection *connection,
120 const char *username, const char *password)
121{
122 const char *headervalue;
123 ...
124
125 headervalue = MHD_lookup_connection_value (connection, MHD_HEADER_KIND,
126 "Authorization");
127 if (NULL == headervalue) return 0;
128@end verbatim
129@noindent
130
131where, firstly, the authentication method will be checked.
132@verbatim
133const char *strbase = "Basic ";
134...
135if (0 != strncmp (headervalue, strbase, strlen (strbase))) return 0;
136@end verbatim
137@noindent
138
139Of course, we could decode the passed credentials in the next step and compare them directly to
140the given strings. But as this would involve string parsing, which is more complicated then string
141composing, it is done the other way around---the clear text credentials will be encoded to @emph{Base64}
142and then compared against the headerline. The authentication method string will be left out here as
143it has been checked already at this point.
144@verbatim
145 char *expected_b64, *expected;
146 int authenticated;
147
148 ...
149 strcpy (expected, username);
150 strcat (expected, ":");
151 strcat (expected, password);
152
153 expected_b64 = string_to_base64 (expected);
154 if (NULL == expected_b64) return 0;
155
156 strcpy (expected, strbase);
157 authenticated = (strcmp (headervalue + strlen (strbase), expected_b64) == 0);
158
159 free (expected_b64);
160
161 return authenticated;
162}
163@end verbatim
164@noindent
165
166These two functions---together with a response function in case of positive authentication doing little
167new---allow the rest of the callback function to be rather short.
168@verbatim
169 if (!is_authenticated (connection, USER, PASSWORD))
170 return ask_for_authentication (connection, REALM);
171
172 return secret_page (connection);
173}
174@end verbatim
175@noindent
176
177See the @code{examples} directory for the complete example file.
178
179@heading Remarks
180For a proper server, the conditional statements leading to a return of @code{MHD_NO} should yield a
181response with a more precise status code instead of silently closing the connection. For example,
182failures of memory allocation are best reported as @emph{internal server error} and unexpected
183authentication methods as @emph{400 bad request}.
184
185@heading Exercises
186@itemize @bullet
187@item
188Make the server respond to wrong credentials (but else correct requests) with the recommended
189@emph{401 unauthorized} status code. If the client still does not authenticate correctly within the
190same connection, close it and store the client's IP address for a certain time. (It is OK to check for
191expiration not until the main thread wakes up again on the next connection.) If the client fails
192authenticating three times during this period, add it to another list whose entries the
193@code{AcceptPolicyCallback} function denies connection (temporally).
194
195@item
196With the network utility @emph{netcat} connect and log the response of a "GET" request as you
197did in the exercise of the first example, this time to a file. Now stop the server and let @emph{netcat}
198listen on the same port the server used to listen on and have it fake being the proper server by giving
199the file's content as the response (e.g. @code{cat log | nc -l -p 8888}). Pretending to think your were
200connecting to the actual server, browse to the eavesdropper and give the correct credentials.
201
202Copy and paste the encoded string you see in netcat's output to some of the Base64 decode tools available online
203and see how both the user's name and password could be completely restored.
204
205@end itemize
206
207