-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathstep7.html
More file actions
204 lines (155 loc) · 8.97 KB
/
step7.html
File metadata and controls
204 lines (155 loc) · 8.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
<html>
<head>
<title>Ur/Web Tutorial</title>
<style>
.code {
background: #d0d0d0;
}
</style>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-10255629-6']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<img src="urheader.png" alt="Ur/Web Tutorial"/></br>
<hr width="99%"/>
<h1>Ur/Web Tutorial</h1>
<h2>Step 7</h2>
<p><a href="step6.html">Previous</a> | <a href="step8.html">Next</a></p>
<p>Obviously from the last step you are free to improve the look and feel of your blog system as you wish. However, we're going to move onto another important feature for any public-facing web application: authentication.</p>
<p>The general scheme for web authentication applies in Ur/Web as it would in almost any other language, except Ur/Web does a few things for us that makes life easier. Cookies are owned by a particular module such that we don't really have to worry too much about other modules misusing our cookies. Cryptographic signing of cookies takes place whenever they are sent to form handlers that might cause database side effects, to protect against cross-site forgery. Finally, because database tables are owned by a module, we can be sure that we're not going to accidentally leak sensitive data.</p>
<p>We're going to go ahead and create a new <span class="code">Auth</span> module, with the relevant files being <span class="code">auth.ur</span> and <span class="code">auth.urs</span>.</p>
<h3>auth.urs</h3>
<p>Our signature file is going to define a couple of helpful externally-visible functions - one that will display a transaction page iff the user is authenticated, and another to permit logging in:</p>
<pre class="code">
val displayIfAuthenticated : transaction page -> transaction page
val login : unit -> transaction page
</pre>
<h3>auth.ur</h3>
<p>We begin skeletally with our implementation. We'll make our display function just return the page no matter what, and we'll design a login form that goes to a nil handler:</p>
<pre class="code">
fun displayIfAuthenticated page = page
fun loginHandler row = return <xml><head/><body/></xml>
fun login () = return
<xml>
<head><title>Login to UrBlog</title></head>
<body>
<form>
<p>Username:<br/><textbox{#Username}/><br/>
Password:<br/><password{#Password}/><br/>
<submit value="Login" action={loginHandler}/>
</p>
</form>
</body>
</xml>
</pre>
<p>This will at least satisfy our signature, despite not being terribly interesting. Let's go ahead and add <span class="code">auth</span> to our <span class="code">urblog.urp</span> file so that our new module will be included:
<h3>urblog.urp</h3>
<pre class="code">
[...]
$/string
auth
crud
urblog
</pre>
<p>The order in which these modules appear is somewhat significant, but it shouldn't take a great deal of effort to figure out how dependencies are resolved in this way.</p>
<h2>Authentication</h2>
<p>The basic mechanism for our authentication is going to involve checking credentials against a <span class="code">user</span> table, and in the case that the credentials match, setting a cookie that includes the username and password. Checking if a user is logged in will then just require re-checking the credentials in the cookie against those in the database.</p>
<p>Defining a cookie is pretty simple in Ur/Web. It involves using the <span class="code">cookie</span> keyword and defining the kinds of data we would like to put into it:</p>
<pre class="code">
cookie userSession : {Username : string, Password : string}
</pre>
<p><strong>Contrite aside: </strong> We're being willfully irresponsible here, putting plaintext passwords into cookies (and indeed, storing passwords in plaintext in a database). In a production system you would want to go through the usual process of applying some kind of cryptographically strong hashing function, but for the moment we're not going to bother to keep things very simple. <strong>It is very important that you do something about this before attempting to run this code in a production setting</strong></p>
<p>We might as well go ahead and add our user table now, too:</p>
<pre class="code">
table user : { Id : int, Username : string, Password : string, Email : string }
PRIMARY KEY Id
</pre>
<p>Naturally, you are free to add some other fields that are of interest to you. Let's go ahead, however, and implement our login handler based on this new table:</p>
<pre class="code">
fun loginHandler row =
re' <- oneOrNoRows1(SELECT user.Id
FROM user
WHERE user.Username = {[row.Username]}
AND user.Password = {[row.Password]});
case re' of
None => error <xml>Invalid Login</xml>
| Some re =>
setCookie userSession
{Value = row,
Expires = None,
Secure = False};
return <xml>
<head><title>Logged in!</title></head>
<body>
<h1>Logged in</h1>
</body>
</xml>
</pre>
<p>Interestingly enough, we've actually just implemented <span class="code">displayIfAuthenticated</span> as well! Well, almost. Our function requires a record with fields <span class="code">Username</span> and <span class="code">Password</span>. Our cookie has this property too! So let's go ahead and generalise this function:</p>
<pre class="code">
fun ifAuthenticated page row =
re' <- oneOrNoRows1(SELECT user.Id
FROM user
WHERE user.Username = {[row.Username]}
AND user.Password = {[row.Password]});
case re' of
None => error <xml>Invalid Login</xml>
| Some re =>
setCookie userSession
{Value = row,
Expires = None,
Secure = False};
page
fun loginHandler row =
ifAuthenticated (return <xml>
<head><title>Logged in!</title></head>
<body>
<h1>Logged in</h1>
</body>
</xml>) row
fun displayIfAuthenticated page =
c <- getCookie userSession;
case c of
None => return
<xml><head/>
<body>
<h1>Not logged in.</h1>
<p><a link={login()}>Login</a></p>
</body>
</xml>
| Some c' => ifAuthenticated page c'
</pre>
<p>So we've replaced our <span class="code">loginHandler</span> function with a call to our new <span class="code">ifAuthenticated</span> helper function, which is also able to be used to implement our <span class="code">displayIfAuthenticated</span> function that retrieves the values from the cookie.</span></p>
<p>So the final thing to do is to wrap any content we want to be password-protected with <span class="code">displayIfAuthenticated</span> calls. Here's one such example from <span class="code">crud.ur</span>:</p>
<pre class="code">
[...]
and admin () =
ls <- editList ();
Auth.displayIfAuthenticated (
return <xml><head>
<title>{cdata M.title}</title>
</head><body>
<h1>{cdata M.title}</h1>
{ls}
</body></xml>)
</pre>
<h2>Testing</h2>
<p>In order to test this, you'll need to add some test data to the <span class="code">uw_Auth_user</span> table. After that, you should be able to visit the 'admin' page (<a href="http://localhost:8080/Urblog/admin">http://localhost:8080/Urblog/admin</a>) and attempt to login using these test credentials. Having logged in successfully, you should be able to revisit the admin link and obtain access.</p>
<h2>Exercises</h2>
<h3>Exercise 7.1</h3>
<p>Adding the other appropriate protections are left as an exercise to the reader. You might also like to add a link to <span class="code">Auth.login</span> somewhere.</p>
<h3>Exercise 7.2</h3>
<p>Creating user accounts is not currently possible without manually editing the database. Can you figure out how to reuse the existing <span class="code">Crud</span> module within the <span class="code">Auth</span> module to implement functionality for adding, modifying and deleting users?</p>
<h3>Exercise 7.3</h3>
<p>Add a logout link to the Auth module.</p>
<p><a href="step6.html">Previous</a> | <a href="step8.html">Next</a></p>
</body>
</html>