diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py
index 1530aae..6f953fc 100644
--- a/IPython/frontend/html/notebook/handlers.py
+++ b/IPython/frontend/html/notebook/handlers.py
@@ -16,6 +16,9 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
+import logging
+import Cookie
+
 from tornado import web
 from tornado import websocket
 
@@ -35,21 +38,46 @@ except ImportError:
 # Top-level handlers
 #-----------------------------------------------------------------------------
 
-
-class NBBrowserHandler(web.RequestHandler):
+class AuthenticatedHandler(web.RequestHandler):
+    """A RequestHandler with an authenticated user."""
+    def get_current_user(self):
+        password = self.get_secure_cookie("password")
+        if password is None:
+            # cookie doesn't exist, or is invalid. Clear to prevent repeated
+            # 'Invalid cookie signature' warnings.
+            self.clear_cookie('password')
+            self.clear_cookie("user_id")
+        if self.application.password and self.application.password != password:
+            return None
+        return self.get_secure_cookie("user") or 'anonymous'
+
+class NBBrowserHandler(AuthenticatedHandler):
+    @web.authenticated
     def get(self):
         nbm = self.application.notebook_manager
         project = nbm.notebook_dir
         self.render('nbbrowser.html', project=project)
 
+class LoginHandler(AuthenticatedHandler):
+    def get(self):
+        user_id = self.get_secure_cookie("user") or ''
+        self.render('login.html', user_id=user_id)
+
+    def post(self):
+        self.set_secure_cookie("user", self.get_argument("name", default=u''))
+        self.set_secure_cookie("password", self.get_argument("password", default=u''))
+        url = self.get_argument("next", default="/")
+        self.redirect(url)
 
-class NewHandler(web.RequestHandler):
+class NewHandler(AuthenticatedHandler):
+    @web.authenticated
     def get(self):
         notebook_id = self.application.notebook_manager.new_notebook()
         self.render('notebook.html', notebook_id=notebook_id)
 
 
-class NamedNotebookHandler(web.RequestHandler):
+class NamedNotebookHandler(AuthenticatedHandler):
+    @web.authenticated
     def get(self, notebook_id):
         nbm = self.application.notebook_manager
         if not nbm.notebook_exists(notebook_id):
@@ -62,12 +90,14 @@ class NamedNotebookHandler(web.RequestHandler):
 #-----------------------------------------------------------------------------
 
 
-class MainKernelHandler(web.RequestHandler):
+class MainKernelHandler(AuthenticatedHandler):
 
+    @web.authenticated
     def get(self):
         km = self.application.kernel_manager
         self.finish(jsonapi.dumps(km.kernel_ids))
 
+    @web.authenticated
     def post(self):
         km = self.application.kernel_manager
         notebook_id = self.get_argument('notebook', default=None)
@@ -78,10 +108,11 @@ class MainKernelHandler(web.RequestHandler):
         self.finish(jsonapi.dumps(data))
 
 
-class KernelHandler(web.RequestHandler):
+class KernelHandler(AuthenticatedHandler):
 
     SUPPORTED_METHODS = ('DELETE')
 
+    @web.authenticated
     def delete(self, kernel_id):
         km = self.application.kernel_manager
         km.kill_kernel(kernel_id)
@@ -89,8 +120,9 @@ class KernelHandler(web.RequestHandler):
         self.finish()
 
 
-class KernelActionHandler(web.RequestHandler):
+class KernelActionHandler(AuthenticatedHandler):
 
+    @web.authenticated
     def post(self, kernel_id, action):
         km = self.application.kernel_manager
         if action == 'interrupt':
@@ -136,20 +168,58 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
         else:
             self.write_message(msg)
 
-
-class IOPubHandler(ZMQStreamHandler):
+class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
+    def open(self, kernel_id):
+        self.kernel_id = kernel_id
+        self.session = Session()
+        self.save_on_message = self.on_message
+        self.on_message = self.on_first_message
+    
+    def get_current_user(self):
+        password = self.get_secure_cookie("password")
+        if password is None:
+            # clear cookies, to prevent future Invalid cookie signature warnings
+            self._cookies = Cookie.SimpleCookie()
+        if self.application.password and self.application.password != password:
+            return None
+        return self.get_secure_cookie("user") or 'anonymous'
+    
+    def _inject_cookie_message(self, msg):
+        """Inject the first message, which is the document cookie,
+        for authentication."""
+        if isinstance(msg, unicode):
+            # Cookie can't constructor doesn't accept unicode strings for some reason
+            msg = msg.encode('utf8', 'replace')
+        try:
+            self._cookies = Cookie.SimpleCookie(msg)
+        except:
+            logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
+    
+    def on_first_message(self, msg):
+        self._inject_cookie_message(msg)
+        if self.get_current_user() is None:
+            logging.warn("Couldn't authenticate WebSocket connection")
+            raise web.HTTPError(403)
+        self.on_message = self.save_on_message
+                
+
+class IOPubHandler(AuthenticatedZMQStreamHandler):
 
     def initialize(self, *args, **kwargs):
         self._kernel_alive = True
         self._beating = False
         self.iopub_stream = None
         self.hb_stream = None
-
-    def open(self, kernel_id):
+    
+    def on_first_message(self, msg):
+        try:
+            super(IOPubHandler, self).on_first_message(msg)
+        except web.HTTPError:
+            self.close()
+            return
         km = self.application.kernel_manager
-        self.kernel_id = kernel_id
-        self.session = Session()
         self.time_to_dead = km.time_to_dead
+        kernel_id = self.kernel_id
         try:
             self.iopub_stream = km.create_iopub_stream(kernel_id)
             self.hb_stream = km.create_hb_stream(kernel_id)
@@ -158,9 +228,13 @@ class IOPubHandler(ZMQStreamHandler):
             # close the connection.
             if not self.stream.closed():
                 self.stream.close()
+            self.close()
         else:
             self.iopub_stream.on_recv(self._on_zmq_reply)
             self.start_hb(self.kernel_died)
+    
+    def on_message(self, msg):
+        pass
 
     def on_close(self):
         # This method can be called twice, once by self.kernel_died and once 
@@ -216,15 +290,20 @@ class IOPubHandler(ZMQStreamHandler):
         self.on_close()
 
 
-class ShellHandler(ZMQStreamHandler):
+class ShellHandler(AuthenticatedZMQStreamHandler):
 
     def initialize(self, *args, **kwargs):
         self.shell_stream = None
 
-    def open(self, kernel_id):
+    def on_first_message(self, msg):
+        try:
+            super(ShellHandler, self).on_first_message(msg)
+        except web.HTTPError:
+            self.close()
+            return
         km = self.application.kernel_manager
         self.max_msg_size = km.max_msg_size
-        self.kernel_id = kernel_id
+        kernel_id = self.kernel_id
         try:
             self.shell_stream = km.create_shell_stream(kernel_id)
         except web.HTTPError:
@@ -232,8 +311,8 @@ class ShellHandler(ZMQStreamHandler):
             # close the connection.
             if not self.stream.closed():
                 self.stream.close()
+            self.close()
         else:
-            self.session = Session()
             self.shell_stream.on_recv(self._on_zmq_reply)
 
     def on_message(self, msg):
@@ -251,13 +330,15 @@ class ShellHandler(ZMQStreamHandler):
 # Notebook web service handlers
 #-----------------------------------------------------------------------------
 
-class NotebookRootHandler(web.RequestHandler):
+class NotebookRootHandler(AuthenticatedHandler):
 
+    @web.authenticated
     def get(self):
         nbm = self.application.notebook_manager
         files = nbm.list_notebooks()
         self.finish(jsonapi.dumps(files))
 
+    @web.authenticated
     def post(self):
         nbm = self.application.notebook_manager
         body = self.request.body.strip()
@@ -271,10 +352,11 @@ class NotebookRootHandler(web.RequestHandler):
         self.finish(jsonapi.dumps(notebook_id))
 
 
-class NotebookHandler(web.RequestHandler):
+class NotebookHandler(AuthenticatedHandler):
 
     SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
 
+    @web.authenticated
     def get(self, notebook_id):
         nbm = self.application.notebook_manager
         format = self.get_argument('format', default='json')
@@ -288,6 +370,7 @@ class NotebookHandler(web.RequestHandler):
         self.set_header('Last-Modified', last_mod)
         self.finish(data)
 
+    @web.authenticated
     def put(self, notebook_id):
         nbm = self.application.notebook_manager
         format = self.get_argument('format', default='json')
@@ -296,6 +379,7 @@ class NotebookHandler(web.RequestHandler):
         self.set_status(204)
         self.finish()
 
+    @web.authenticated
     def delete(self, notebook_id):
         nbm = self.application.notebook_manager
         nbm.delete_notebook(notebook_id)
@@ -307,8 +391,9 @@ class NotebookHandler(web.RequestHandler):
 #-----------------------------------------------------------------------------
 
 
-class RSTHandler(web.RequestHandler):
+class RSTHandler(AuthenticatedHandler):
 
+    @web.authenticated
     def post(self):
         if publish_string is None:
             raise web.HTTPError(503, u'docutils not available')
diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py
index 5821481..067f0db 100644
--- a/IPython/frontend/html/notebook/notebookapp.py
+++ b/IPython/frontend/html/notebook/notebookapp.py
@@ -35,7 +35,7 @@ from tornado import httpserver
 from tornado import web
 
 from .kernelmanager import MappingKernelManager
-from .handlers import (
+from .handlers import (LoginHandler,
     NBBrowserHandler, NewHandler, NamedNotebookHandler,
     MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
     ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
@@ -80,6 +80,7 @@ class NotebookWebApplication(web.Application):
     def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
         handlers = [
             (r"/", NBBrowserHandler),
+            (r"/login", LoginHandler),
             (r"/new", NewHandler),
             (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
             (r"/kernels", MainKernelHandler),
@@ -94,6 +95,8 @@ class NotebookWebApplication(web.Application):
         settings = dict(
             template_path=os.path.join(os.path.dirname(__file__), "templates"),
             static_path=os.path.join(os.path.dirname(__file__), "static"),
+            cookie_secret=os.urandom(1024),
+            login_url="/login",
         )
         web.Application.__init__(self, handlers, **settings)
 
@@ -122,7 +125,7 @@ aliases.update({
     'keyfile': 'IPythonNotebookApp.keyfile',
     'certfile': 'IPythonNotebookApp.certfile',
     'ws-hostname': 'IPythonNotebookApp.ws_hostname',
-    'notebook-dir': 'NotebookManager.notebook_dir'
+    'notebook-dir': 'NotebookManager.notebook_dir',
 })
 
 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
@@ -185,6 +188,10 @@ class IPythonNotebookApp(BaseIPythonApplication):
         help="""The full path to a private key file for usage with SSL/TLS."""
     )
 
+    password = Unicode(u'', config=True,
+                      help="""Password to use for web authentication"""
+    )
+
     def get_ws_url(self):
         """Return the WebSocket URL for this server."""
         if self.certfile:
@@ -241,6 +248,7 @@ class IPythonNotebookApp(BaseIPythonApplication):
                 ssl_options['keyfile'] = self.keyfile
         else:
             ssl_options = None
+        self.web_app.password = self.password
         self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
         if ssl_options is None and not self.ip:
             self.log.critical('WARNING: the notebook server is listening on all IP addresses '
diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js
index ef7ea0c..137d59c 100644
--- a/IPython/frontend/html/notebook/static/js/kernel.js
+++ b/IPython/frontend/html/notebook/static/js/kernel.js
@@ -95,6 +95,12 @@ var IPython = (function (IPython) {
         console.log("Starting WS:", ws_url);
         this.shell_channel = new this.WebSocket(ws_url + "/shell");
         this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
+        send_cookie = function(){
+            this.send(document.cookie);
+            console.log(this);
+        }
+        this.shell_channel.onopen = send_cookie;
+        this.iopub_channel.onopen = send_cookie;
     };
 
 
diff --git a/IPython/frontend/html/notebook/templates/login.html b/IPython/frontend/html/notebook/templates/login.html
new file mode 100644
index 0000000..855db58
--- /dev/null
+++ b/IPython/frontend/html/notebook/templates/login.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+    <meta charset="utf-8">
+
+    <title>IPython Notebook</title>
+
+    <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
+<!--     <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
+<!--    <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
+
+    <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
+    <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
+    <link rel="stylesheet" href="static/css/base.css" type="text/css" />
+<script type="text/javascript" charset="utf-8">
+function add_next_to_action(){
+    // add 'next' argument to action url, to preserve redirect
+    var query = location.search.substring(1);
+    var form = document.forms[0];
+    var action = form.getAttribute("action");
+    form.setAttribute("action", action + '?' + query);
+}
+</script>
+</head>
+
+<body onload="add_next_to_action()">
+
+<div id="header">
+    <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
+</div>
+
+<div id="header_border"></div>
+
+<div id="main_app">
+
+    <div id="app_hbox">
+
+    <div id="left_panel">
+    </div>
+    
+    <div id="content_panel">
+        <form action="/login" method="post">
+            Name: <input type="text" name="name" value="{{user_id}}">
+            Password: <input type="password" name="password">
+            <input type="submit" value="Sign in">
+        </form>
+    </div>
+    <div id="right_panel">
+    </div>
+    
+    </div>
+
+</div>
+
+<script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
+<script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
+<script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
+<script src="static/js/nbbrowser_main.js" type="text/javascript" charset="utf-8"></script>
+
+</body>
+
+</html>
+
+