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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
|
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-us">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Cross-domain Backbone.js with sessions using CORS - Backbone.js Tutorials</title>
<link href="/atom.xml" rel="alternate" title="backbone tutorials" type="application/atom+xml">
<meta name="author" content="Backbone Tutorials" />
<!-- syntax highlighting CSS -->
<!-- syntax highlighting CSS -->
<link rel="stylesheet" href="/css/syntax.css" type="text/css" />
<!-- Homepage CSS -->
<link rel="stylesheet" href="//bootswatch.com/sandstone/bootstrap.min.css" type="text/css" media="screen, projection" />
<link rel="stylesheet" href="/css/style.css" type="text/css" media="screen, projection" />
<link href="https://fonts.googleapis.com/css?family=Arvo:400,700&subset=latin" rel="stylesheet" type="text/css">
<!-- Typekit -->
<script type="text/javascript">try{Typekit.load();}catch(e){}</script>
</head>
<body>
<div class="row">
<div class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Backbone Tutorials</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li class=""><a href="/">Tutorials</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="http://prerender.io"><strong>Need SEO?</strong></a></li>
<li><a href="https://leanpub.com/backbonetutorials">Download eBook (.pdf, .MOBI, .ePub)</a></li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">About</h3>
</div>
<div class="panel-body">
<div class="col-lg-4">
<p>Backbone Tutorials is a collection of tutorials written by <a href="http://thomasdav.is">Thomas Davis</a>. Everything is open source and I try my best to keep the tutorials updated. Though I am busy and only work on this is my spare time so many <a href="https://github.com/thomasdavis/backbonetutorials/graphs/contributors">contributors</a> have also help me put this resource together.</p>
<a href="https://twitter.com/neutralthoughts" class="twitter-follow-button" data-show-count="true">Follow @neutralthoughts</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
</div>
<div class="col-lg-8">
<h3 >Backbone.js Beginner Video Tutorial</h3>
<img src="/backbone.png" style="float: left;" /><p>I have put extra effort into making a very easy to understand Backbone.js video which is also free. It is 70mins long and covers everything you need to know when getting started.</p>
<a href="https://www.youtube.com/watch?v=FZSjvWtUxYk" class="btn btn-primary">Watch Video</a>
</div>
</div>
</div>
</div>
<div id="post">
<h1>Cross-domain Backbone.js with sessions using CORS</h1>
<p>** This tutorial is a proof of concept and needs to be checked for security flaws **</p>
<p>This tutorial will teach you how to completely separate the server and client allowing for developers to work with freedom in their respective areas.</p>
<p>On a personal note, I consider this development practice highly desirable and encourage others to think of the possible benefits but the security still needs to be proved.</p>
<blockquote>
<p>Cross-Origin Resource Sharing (CORS) is a specification that enables a truly open access across domain-boundaries. - <a href="http://enable-cors.org/">enable-cors.org</a></p>
</blockquote>
<p><strong>Some benefits include</strong></p>
<ul>
<li>The client and back end exist independently regardless of where they are each hosted and built.</li>
<li>Due to the separation of concerns, testing now becomes easier and more controlled.</li>
<li>Develop only one API on the server, your front-end could be outsourced or built by a in-house team.</li>
<li>As a front-end developer you can host the client anywhere.</li>
<li>This separation enforces that the API be built robustly, documented, collaboratively and versioned.</li>
</ul>
<p>** Cons of this tutorial **</p>
<ul>
<li>This tutorial doesn't explain how to perform this with cross browser support. CORS headers aren't supported by Opera and IE 6/7. Though it is do-able using <a href="http://easyxdm.net/wp/">easyXDM</a></li>
<li>Security is somewhat addressed but maybe a more thorough security expert can chime in.</li>
</ul>
<h2>Security</h2>
<ul>
<li>Don't allow GET request to change data, only retrieve.</li>
<li>Whitelist your allowed domains (see <a href="https://github.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/cross-domain/server.js">server.js</a>)</li>
<li>Protect again <a href="http://blog.opensecurityresearch.com/2012/02/json-csrf-with-parameter-padding.html">JSON padding</a></li>
</ul>
<h2>Getting started</h2>
<p>To easily understand this tutorial you should jump straight into the example code base.</p>
<p>Host the codebase on a simple HTTP server such that the domain is <code>localhost</code> with port 80 hidden.</p>
<p><a href="https://github.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/cross-domain">Example Codebase</a></p>
<p><a href="http://backbonetutorials.com/examples/cross-domain/">Example Demo</a></p>
<p>This tutorial focuses on building a flexible Session model to control session state in your application.</p>
<h2>Checking session state at first load</h2>
<p>Before starting any routes, we should really know whether the user is authenticated. This will allow us to load the appropriate views. We will simply wrap our <code>Backbone.history.start</code> in a callback that executes after <code>Session.getAuth</code> has checked the server. We will jump into our Session model next.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">define</span><span class="p">([</span>
<span class="s1">'jquery'</span><span class="p">,</span>
<span class="s1">'underscore'</span><span class="p">,</span>
<span class="s1">'backbone'</span><span class="p">,</span>
<span class="s1">'vm'</span><span class="p">,</span>
<span class="s1">'events'</span><span class="p">,</span>
<span class="s1">'models/session'</span><span class="p">,</span>
<span class="s1">'text!templates/layout.html'</span>
<span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">$</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">Backbone</span><span class="p">,</span> <span class="nx">Vm</span><span class="p">,</span> <span class="nx">Events</span><span class="p">,</span> <span class="nx">Session</span><span class="p">,</span> <span class="nx">layoutTemplate</span><span class="p">){</span>
<span class="kd">var</span> <span class="nx">AppView</span> <span class="o">=</span> <span class="nx">Backbone</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">el</span><span class="o">:</span> <span class="s1">'.container'</span><span class="p">,</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajaxPrefilter</span><span class="p">(</span> <span class="kd">function</span><span class="p">(</span> <span class="nx">options</span><span class="p">,</span> <span class="nx">originalOptions</span><span class="p">,</span> <span class="nx">jqXHR</span> <span class="p">)</span> <span class="p">{</span>
<span class="c1">// Your server goes below</span>
<span class="c1">//options.url = 'http://localhost:8000' + options.url;</span>
<span class="nx">options</span><span class="p">.</span><span class="nx">url</span> <span class="o">=</span> <span class="s1">'http://cross-domain.nodejitsu.com'</span> <span class="o">+</span> <span class="nx">options</span><span class="p">.</span><span class="nx">url</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nx">render</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">that</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="nx">$</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">el</span><span class="p">).</span><span class="nx">html</span><span class="p">(</span><span class="nx">layoutTemplate</span><span class="p">);</span>
<span class="c1">// This is the entry point to your app, therefore</span>
<span class="c1">// when the user refreshes the page we should</span>
<span class="c1">// really know if they're authed. We will give it</span>
<span class="c1">// A call back when we know what the auth status is</span>
<span class="nx">Session</span><span class="p">.</span><span class="nx">getAuth</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Backbone</span><span class="p">.</span><span class="nx">history</span><span class="p">.</span><span class="nx">start</span><span class="p">();</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">AppView</span><span class="p">;</span>
<span class="p">});</span></code></pre></div>
<p><em>Note: We have used jQuery <code>ajaxPrefilter</code> to hook into all AJAX requests before they are executed. This is where we specify what server we want the application to hit.</em></p>
<h2>An example Session model</h2>
<p>This is a very light weight Session model which handles most situations. Read through the code and comments below. The model simply has a login, logout and check function. Again we have hooked into jQuery <code>ajaxPrefilter</code> to allow for csrf tokens and also telling jQuery to send cookies with the <code>withCredentials</code> property. The model relies heavily on it's <code>auth</code> property. Throughout your application, each view can simply bind to <code>change:auth</code> on the Session model and react accordingly. Because we return this AMD module instantiated using the new keyword, then it will keep state throughout the page. (This may not be best practice but it's highly convenient)</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// views/app.js</span>
<span class="nx">define</span><span class="p">([</span>
<span class="s1">'underscore'</span><span class="p">,</span>
<span class="s1">'backbone'</span>
<span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="nx">Backbone</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">SessionModel</span> <span class="o">=</span> <span class="nx">Backbone</span><span class="p">.</span><span class="nx">Model</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">urlRoot</span><span class="o">:</span> <span class="s1">'/session'</span><span class="p">,</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">that</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="c1">// Hook into jquery</span>
<span class="c1">// Use withCredentials to send the server cookies</span>
<span class="c1">// The server must allow this through response headers</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajaxPrefilter</span><span class="p">(</span> <span class="kd">function</span><span class="p">(</span> <span class="nx">options</span><span class="p">,</span> <span class="nx">originalOptions</span><span class="p">,</span> <span class="nx">jqXHR</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">options</span><span class="p">.</span><span class="nx">xhrFields</span> <span class="o">=</span> <span class="p">{</span>
<span class="nx">withCredentials</span><span class="o">:</span> <span class="kc">true</span>
<span class="p">};</span>
<span class="c1">// If we have a csrf token send it through with the next request</span>
<span class="k">if</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">that</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'_csrf'</span><span class="p">)</span> <span class="o">!==</span> <span class="s1">'undefined'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">jqXHR</span><span class="p">.</span><span class="nx">setRequestHeader</span><span class="p">(</span><span class="s1">'X-CSRF-Token'</span><span class="p">,</span> <span class="nx">that</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'_csrf'</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nx">login</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">creds</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Do a POST to /session and send the serialized form creds</span>
<span class="k">this</span><span class="p">.</span><span class="nx">save</span><span class="p">(</span><span class="nx">creds</span><span class="p">,</span> <span class="p">{</span>
<span class="nx">success</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{}</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nx">logout</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Do a DELETE to /session and clear the clientside data</span>
<span class="kd">var</span> <span class="nx">that</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">destroy</span><span class="p">({</span>
<span class="nx">success</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">model</span><span class="p">,</span> <span class="nx">resp</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">model</span><span class="p">.</span><span class="nx">clear</span><span class="p">()</span>
<span class="nx">model</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="c1">// Set auth to false to trigger a change:auth event</span>
<span class="c1">// The server also returns a new csrf token so that</span>
<span class="c1">// the user can relogin without refreshing the page</span>
<span class="nx">that</span><span class="p">.</span><span class="nx">set</span><span class="p">({</span><span class="nx">auth</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">_csrf</span><span class="o">:</span> <span class="nx">resp</span><span class="p">.</span><span class="nx">_csrf</span><span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nx">getAuth</span><span class="o">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// getAuth is wrapped around our router</span>
<span class="c1">// before we start any routers let us see if the user is valid</span>
<span class="k">this</span><span class="p">.</span><span class="nx">fetch</span><span class="p">({</span>
<span class="nx">success</span><span class="o">:</span> <span class="nx">callback</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">SessionModel</span><span class="p">();</span>
<span class="p">});</span></code></pre></div>
<p><em>Note: This session model is missing one useful feature. If a user looses auth when navigating your application then the application should set {auth: false} on this model. To do this, in the <code>ajaxPrefilter</code> edit outgoing <code>success</code> functions to check if the server response was {auth: false} and then call the original <code>success()</code> function.</em></p>
<h2>Hooking up views to listen to changes in <code>auth</code></h2>
<p>Now that we have a Session model, let's hook up our <code>login/logout</code> view to listen to changes in <code>auth</code>. When creating the view we use <code>on</code> to bind a listener to the <code>auth</code> attribute of our model. Everytime it changes we will re-render the view which will conditionally load a template depending on the value of <code>Session.get('auth')</code>.</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// models/session.js</span>
<span class="nx">define</span><span class="p">([</span>
<span class="s1">'jquery'</span><span class="p">,</span>
<span class="s1">'underscore'</span><span class="p">,</span>
<span class="s1">'backbone'</span><span class="p">,</span>
<span class="s1">'models/session'</span><span class="p">,</span>
<span class="s1">'text!templates/example/login.html'</span><span class="p">,</span>
<span class="s1">'text!templates/example/logout.html'</span>
<span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">$</span><span class="p">,</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">Backbone</span><span class="p">,</span> <span class="nx">Session</span><span class="p">,</span> <span class="nx">exampleLoginTemplate</span><span class="p">,</span> <span class="nx">exampleLogoutTemplate</span><span class="p">){</span>
<span class="kd">var</span> <span class="nx">ExamplePage</span> <span class="o">=</span> <span class="nx">Backbone</span><span class="p">.</span><span class="nx">View</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
<span class="nx">el</span><span class="o">:</span> <span class="s1">'.page'</span><span class="p">,</span>
<span class="nx">initialize</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">that</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="c1">// Bind to the Session auth attribute so we</span>
<span class="c1">// make our view act recordingly when auth changes</span>
<span class="nx">Session</span><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'change:auth'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">session</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">that</span><span class="p">.</span><span class="nx">render</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nx">render</span><span class="o">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Simply choose which template to choose depending on</span>
<span class="c1">// our Session models auth attribute</span>
<span class="k">if</span><span class="p">(</span><span class="nx">Session</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'auth'</span><span class="p">)){</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$el</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">_</span><span class="p">.</span><span class="nx">template</span><span class="p">(</span><span class="nx">exampleLogoutTemplate</span><span class="p">,</span> <span class="p">{</span><span class="nx">username</span><span class="o">:</span> <span class="nx">Session</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'username'</span><span class="p">)}));</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">$el</span><span class="p">.</span><span class="nx">html</span><span class="p">(</span><span class="nx">exampleLoginTemplate</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="nx">events</span><span class="o">:</span> <span class="p">{</span>
<span class="s1">'submit form.login'</span><span class="o">:</span> <span class="s1">'login'</span><span class="p">,</span> <span class="c1">// On form submission</span>
<span class="s1">'click .logout'</span><span class="o">:</span> <span class="s1">'logout'</span>
<span class="p">},</span>
<span class="nx">login</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">ev</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Disable the button</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'[type=submit]'</span><span class="p">,</span> <span class="nx">ev</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">).</span><span class="nx">val</span><span class="p">(</span><span class="s1">'Logging in'</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s1">'disabled'</span><span class="p">,</span> <span class="s1">'disabled'</span><span class="p">);</span>
<span class="c1">// Serialize the form into an object using a jQuery plgin</span>
<span class="kd">var</span> <span class="nx">creds</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="nx">ev</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">).</span><span class="nx">serializeObject</span><span class="p">();</span>
<span class="nx">Session</span><span class="p">.</span><span class="nx">login</span><span class="p">(</span><span class="nx">creds</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">},</span>
<span class="nx">logout</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">ev</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Disable the button</span>
<span class="nx">$</span><span class="p">(</span><span class="nx">ev</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">).</span><span class="nx">text</span><span class="p">(</span><span class="s1">'Logging out'</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="s1">'disabled'</span><span class="p">,</span> <span class="s1">'disabled'</span><span class="p">);</span>
<span class="nx">Session</span><span class="p">.</span><span class="nx">logout</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">ExamplePage</span><span class="p">;</span>
<span class="p">});</span></code></pre></div>
<p><em>Note: <code>.serializeObject</code> is not a native jQuery function and I have included it as <a href="https://github.com/thomasdavis/backbonetutorials/blob/gh-pages/examples/cross-domain/js/views/app.js">app.js</a> in the demo folder. <code>creds</code> can be an object of any variation of inputs, regardless it will be converted to JSON and posted to the server like any normal Backbone model.</em></p>
<p>Here are the templates we are using for our login view</p>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c"><!--</span> <span class="nx">templates</span><span class="o">/</span><span class="nx">example</span><span class="o">/</span><span class="nx">login</span><span class="p">.</span><span class="nx">html</span> <span class="o">--></span>
<span class="o"><</span><span class="nx">form</span> <span class="kr">class</span><span class="o">=</span><span class="s2">"login"</span><span class="o">></span>
<span class="o"><</span><span class="nx">label</span> <span class="k">for</span><span class="o">=</span><span class="s2">""</span><span class="o">></span><span class="nx">Username</span><span class="o"><</span><span class="err">/label></span>
<span class="o"><</span><span class="nx">input</span> <span class="nx">name</span><span class="o">=</span><span class="s2">"username"</span> <span class="nx">type</span><span class="o">=</span><span class="s2">"text"</span> <span class="nx">required</span> <span class="nx">autofocus</span><span class="o">></span>
<span class="o"><</span><span class="nx">input</span> <span class="nx">type</span><span class="o">=</span><span class="s2">"submit"</span> <span class="nx">id</span><span class="o">=</span><span class="s2">"submit"</span> <span class="nx">value</span><span class="o">=</span><span class="s2">"Login"</span><span class="o">></span>
<span class="o"><</span><span class="err">/form></span>
<span class="c"><!--</span> <span class="nx">templates</span><span class="o">/</span><span class="nx">example</span><span class="o">/</span><span class="nx">logout</span><span class="p">.</span><span class="nx">html</span> <span class="o">--></span>
<span class="o"><</span><span class="nx">p</span><span class="o">></span><span class="nx">Hello</span><span class="p">,</span> <span class="o"><%=</span> <span class="nx">username</span> <span class="o">%></span><span class="p">.</span> <span class="nx">Time</span> <span class="nx">to</span> <span class="nx">logout</span><span class="o">?<</span><span class="err">/p></span>
<span class="o"><</span><span class="nx">button</span> <span class="kr">class</span><span class="o">=</span><span class="s2">"logout"</span><span class="o">></span><span class="nx">Logout</span><span class="o"><</span><span class="err">/button></span></code></pre></div>
<p>This wraps up setting up the client, there are some notable points to make sure this technique works.</p>
<ul>
<li>You must use <code>withCredentials</code> supplied by jQuery - session.js</li>
<li>You must send your request with csrf tokens for security - session.js</li>
<li>You should wrap your applications entry pointer (router in this example) in a check auth function - app.js</li>
<li>You must point your application at the right server - app.js</li>
</ul>
<h2>Building a compatible server</h2>
<p>This tutorial uses node.js, express.js and a modified csrf.js library. An example server.js file exist in the <code>examples/cross-domain</code> folder. When inside the folder simply type <code>npm install -d</code> to install the dependencies and then <code>node server.js</code> to start the server. Again, make sure your <code>app.js</code> points at the correct server.</p>
<p>The server has to do a few things;</p>
<ul>
<li>Allow CORS request</li>
<li>Implement csrf protection</li>
<li>Allow jQuery to send credentials</li>
<li>Set a whitelist of allowed domains</li>
<li>Configure the correct response headers</li>
</ul>
<p>To save you sometime here are some gotchas;</p>
<ul>
<li>When sending <code>withCredentials</code> you must set correct response header <code>Access-Control-Allow-Credentials: true</code>. Also as a security policy browsers do not allow <code>Access-Control-Allow-Origin</code> to be set to <code>*</code>. So the origin of the request has to be known and trusted, so in the example below we use an of white listed domains.</li>
<li>jQuery ajax will trigger the browser to send these headers to enforce security <code>origin, x-requested-with, accept</code> so our server must allow them.</li>
<li>The browser might send out a <code>pre-flight</code> request to verify that it can talk to the server. The server must return <code>200 OK</code> on these <code>pre-flight</code> request.</li>
</ul>
<p>Be sure to read this Mozilla <a href="http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/">documentation</a> on the above.</p>
<h2>Example node server</h2>
<p>This server below implements everything we have talked about so far. It should be relatively easy to see how would translate into other frameworks and languages. <code>app.configure</code> runs the specified libraries against every request. We have told the server that on each request it should check the csrf token and check if the origin domain is white-listed. If so we edit each request to contain the appropriate headers.</p>
<p>This server has 3 endpoints, that are pseudo-restful;</p>
<ul>
<li>POST /session - Login - Sets the session username and returns a csrf token for the user to use</li>
<li>DELETE /session - Logout - Destroys the session and regenerates a new csrf token if the user wants to re-login</li>
<li>GET /session - Checks Auth - Simply returns if auth is true or false, if true then also returns some session details</li>
</ul>
<div class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">express</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'express'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">connect</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'connect'</span><span class="p">);</span>
<span class="c1">// Custom csrf library</span>
<span class="kd">var</span> <span class="nx">csrf</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'./csrf'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">.</span><span class="nx">createServer</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">allowCrossDomain</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Added other domains you want the server to give access to</span>
<span class="c1">// WARNING - Be careful with what origins you give access to</span>
<span class="kd">var</span> <span class="nx">allowedHost</span> <span class="o">=</span> <span class="p">[</span>
<span class="s1">'http://backbonetutorials.com'</span><span class="p">,</span>
<span class="s1">'http://localhost'</span>
<span class="p">];</span>
<span class="k">if</span><span class="p">(</span><span class="nx">allowedHost</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="nx">req</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">origin</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="s1">'Access-Control-Allow-Credentials'</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="s1">'Access-Control-Allow-Origin'</span><span class="p">,</span> <span class="nx">req</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">origin</span><span class="p">)</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="s1">'Access-Control-Allow-Methods'</span><span class="p">,</span> <span class="s1">'GET,PUT,POST,DELETE,OPTIONS'</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">header</span><span class="p">(</span><span class="s1">'Access-Control-Allow-Headers'</span><span class="p">,</span> <span class="s1">'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'</span><span class="p">);</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span><span class="nx">auth</span><span class="o">:</span> <span class="kc">false</span><span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">configure</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">cookieParser</span><span class="p">());</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">session</span><span class="p">({</span> <span class="nx">secret</span><span class="o">:</span> <span class="s1">'thomasdavislovessalmon'</span> <span class="p">}));</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">bodyParser</span><span class="p">());</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">allowCrossDomain</span><span class="p">);</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">csrf</span><span class="p">.</span><span class="nx">check</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'/session'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">){</span>
<span class="c1">// This checks the current users auth</span>
<span class="c1">// It runs before Backbones router is started</span>
<span class="c1">// we should return a csrf token for Backbone to use</span>
<span class="k">if</span><span class="p">(</span><span class="k">typeof</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">username</span> <span class="o">!==</span> <span class="s1">'undefined'</span><span class="p">){</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span><span class="nx">auth</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">id</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">username</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">username</span><span class="p">,</span> <span class="nx">_csrf</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">_csrf</span><span class="p">});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span><span class="nx">auth</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">_csrf</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">_csrf</span><span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">'/session'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">){</span>
<span class="c1">// Login</span>
<span class="c1">// Here you would pull down your user credentials and match them up</span>
<span class="c1">// to the request</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">username</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span><span class="nx">auth</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">id</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">username</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">username</span><span class="p">});</span>
<span class="p">});</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">del</span><span class="p">(</span><span class="s1">'/session/:id'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">){</span>
<span class="c1">// Logout by clearing the session</span>
<span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">regenerate</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">err</span><span class="p">){</span>
<span class="c1">// Generate a new csrf token so the user can login again</span>
<span class="c1">// This is pretty hacky, connect.csrf isn't built for rest</span>
<span class="c1">// I will probably release a restful csrf module</span>
<span class="nx">csrf</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">send</span><span class="p">({</span><span class="nx">auth</span><span class="o">:</span> <span class="kc">false</span><span class="p">,</span> <span class="nx">_csrf</span><span class="o">:</span> <span class="nx">req</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">_csrf</span><span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="mi">8000</span><span class="p">);</span></code></pre></div>
<p><em>Note: I wrote a custom csrf module for this which can be found in the example directory. It's based of connects and uses the <code>crypto</code> library. I didn't spend much time on it but other traditional csrf modules won't work because they aren't exactly built for this implementation technique.</em></p>
<h2>Conclusion</h2>
<p>This approach really hammers in the need for a well documented and designed API. A powerful API will let you do application iterations with ease.</p>
<p>Again, it would be great for some more analysis of the security model.</p>
<p>Enjoy using Backbone.js cross domain!</p>
<p><em>I cannot get passed the spam filter on HackerNews so feel free to submit this tutorial</em></p>
<p><a href="https://github.com/thomasdavis/backbonetutorials/tree/gh-pages/examples/cross-domain">Example Codebase</a></p>
<p><a href="http://backbonetutorials.com/examples/cross-domain/">Example Demo</a></p>
<h3>Relevant Links</h3>
<ul>
<li><a href="http://hacks.mozilla.org/2009/07/cross-site-xmlhttprequest-with-cors/">cross-site xmlhttprequest with CORS</a></li>
<li><a href="http://www.w3.org/TR/cors/">Cross-Origin Resource Sharing</a></li>
<li><a href="http://www.kendoui.com/blogs/teamblog/posts/11-10-04/using_cors_with_all_modern_browsers.aspx">Using CORS with All (Modern) Browsers</a></li>
</ul>
</div>
<hr />
<div class="row">
<div class="col-lg-4 col-lg-offset-4">
<div style="text-align: center;">
<img src="https://secure.gravatar.com/avatar/cff733cf90823edd218a834980379c61?s=170" class="img-circle" style=" padding:2px; border: 1px solid #ddd; width: 100px;">
<h2>Thomas Davis</h2>
<p>Founder of <a href="http://cdnjs.com">cdnjs.com</a>, <a href="http://jsonresume.org">jsonresume.org</a></p>
<p>Work with Drones, Open Source, Tech Policy, Javascript and Music. </p>
<div class="addthis_horizontal_follow_toolbox" style="padding-left: 33px;"></div>
<br />
<a href="https://github.com/thomasdavis">github.com/thomasdavis</a>
</div>
</div>
</div>
<hr />
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=thomasdavis" async="async"></script>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'bbtutes'; // required: replace example with your forum shortname
var disqus_url = 'http://backbonetutorials.com/cross-domain-sessions';
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
</div>
<script src="//static.getclicky.com/js" type="text/javascript"></script>
<script type="text/javascript">try{ clicky.init(66406579); }catch(e){}</script>
<noscript><p><img alt="Clicky" width="1" height="1" src="//in.getclicky.com/66406579ns.gif" /></p></noscript>
</body>
</html>
|