diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 46d7c24d2dcdca2ef66efa86c16199e520d96fb3..1464ccddcfaa0b81b23a05b2a4813d8ad5a34a7a 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -2,9 +2,11 @@ from util.json_request import expect_json
 import json
 
 from django.http import HttpResponse
+from django.core.context_processors import csrf
 from django_future.csrf import ensure_csrf_cookie
-from fs.osfs import OSFS
 from django.core.urlresolvers import reverse
+from fs.osfs import OSFS
+
 from xmodule.modulestore import Location
 from github_sync import export_to_github
 
@@ -25,6 +27,14 @@ def index(request):
     })
 
 
+@ensure_csrf_cookie
+def signup(request):
+    """
+    Display the signup form.
+    """
+    csrf_token = csrf(request)['csrf_token']
+    return render_to_response('signup.html', {'csrf': csrf_token }) 
+
 @ensure_csrf_cookie
 def course_index(request, org, course, name):
     # TODO (cpennington): These need to be read in from the active user
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 8d3e2672daab0a85da9979b3ab652254912f3714..7563ea873144c6bb2529dc66fe6093b6fef84d40 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -34,6 +34,10 @@ MITX_FEATURES = {
     'GITHUB_PUSH': False,
 }
 
+# needed to use lms student app
+GENERATE_RANDOM_USER_CREDENTIALS = False
+
+
 ############################# SET PATH INFORMATION #############################
 PROJECT_ROOT = path(__file__).abspath().dirname().dirname()  # /mitx/cms
 REPO_ROOT = PROJECT_ROOT.dirname()
@@ -97,7 +101,7 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
 
-    # Instead of AuthenticationMiddleware, we use a cached backed version
+    # Instead of AuthenticationMiddleware, we use a cache-backed version
     'cache_toolbox.middleware.CacheBackedAuthenticationMiddleware',
 
     'django.contrib.messages.middleware.MessageMiddleware',
@@ -239,9 +243,11 @@ INSTALLED_APPS = (
     'django.contrib.sessions',
     'django.contrib.sites',
     'django.contrib.messages',
+    'south',
 
     # For CMS
     'contentstore',
+    'student',  # misleading name due to sharing with lms
 
     # For asset pipelining
     'pipeline',
diff --git a/cms/envs/dev.py b/cms/envs/dev.py
index 465d542c5be66e8084d51f567683dd16ebba6117..7bc4e427d21b8ebdee6733ff609811fc76e9b1bd 100644
--- a/cms/envs/dev.py
+++ b/cms/envs/dev.py
@@ -25,7 +25,7 @@ MODULESTORE = {
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': ENV_ROOT / "db" / "mitx.db",
+        'NAME': ENV_ROOT / "db" / "cms.db",
     }
 }
 
diff --git a/cms/static/js/main.js b/cms/static/js/main.js
index 2d72edc4bf49c448091adce915dbd45d51b82792..4a9b5d23746eb6a09e3ff9a8b9617a3023a07ff3 100644
--- a/cms/static/js/main.js
+++ b/cms/static/js/main.js
@@ -80,6 +80,6 @@ $(document).ready(function(){
     $('section.problem-edit').show();
     return false;
   });
-
+    
 });
 
diff --git a/cms/templates/activation_active.html b/cms/templates/activation_active.html
new file mode 100644
index 0000000000000000000000000000000000000000..79ba2e39f18a2d763568fdfd36c1481b2a9b004a
--- /dev/null
+++ b/cms/templates/activation_active.html
@@ -0,0 +1,15 @@
+<%inherit file="marketing.html" />
+
+<%block name="content">
+
+<section class="tos">
+<div>
+
+<section class="activation">
+  <h1>Account already active!</h1>
+  <p> This account has already been activated. You can log in at
+  the <a href="/">home page</a>.</p>
+</div>
+</section>
+
+</%block>
\ No newline at end of file
diff --git a/cms/templates/activation_complete.html b/cms/templates/activation_complete.html
new file mode 100644
index 0000000000000000000000000000000000000000..ede9b372ee1346e5459f7eea605fb1905882045a
--- /dev/null
+++ b/cms/templates/activation_complete.html
@@ -0,0 +1,13 @@
+<%inherit file="marketing.html" />
+
+
+<%block name="content">
+
+<section class="tos">
+<div>
+  <h1>Activation Complete!</h1>
+  <p>Thanks for activating your account. You can log in at the <a href="/">home page</a>.</p>
+</div>
+</section>
+
+</%block>
\ No newline at end of file
diff --git a/cms/templates/activation_invalid.html b/cms/templates/activation_invalid.html
new file mode 100644
index 0000000000000000000000000000000000000000..f8b9e585de25d757aea93feab15e1bfefaa1ab7b
--- /dev/null
+++ b/cms/templates/activation_invalid.html
@@ -0,0 +1,16 @@
+<%inherit file="marketing.html" />
+
+<%block name="content">
+<section class="tos">
+<div>
+<h1>Activation Invalid</h1>
+
+<p>Something went wrong. Check to make sure the URL you went to was
+  correct -- e-mail programs will sometimes split it into two
+  lines. If you still have issues, e-mail us to let us know what happened
+  at <a href="mailto:bugs@mitx.mit.edu">bugs@mitx.mit.edu</a>.</p>
+
+  <p>Or you can go back to the <a href="/">home page</a>.</p>
+</div>
+</section>
+</%block>
\ No newline at end of file
diff --git a/cms/templates/base.html b/cms/templates/base.html
index dbae876eca438cb31a7ccf1b43d81e9d07551902..1092147efd067ca2e87d9d90e6a4373ef73653a6 100644
--- a/cms/templates/base.html
+++ b/cms/templates/base.html
@@ -21,8 +21,6 @@
 
     <%include file="widgets/header.html"/>
 
-    <%block name="content"></%block>
-
     <script type="text/javascript" src="${static.url('js/vendor/jquery.min.js')}"></script>
     <script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
     <script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
@@ -40,6 +38,9 @@
     <script src="${static.url('js/vendor/jquery.cookie.js')}"></script>
     <script src="${static.url('js/vendor/jquery.leanModal.min.js')}"></script>
     <script src="${static.url('js/vendor/jquery.tablednd.js')}"></script>
+
+    <%block name="content"></%block>
+
   </body>
 </html>
 
diff --git a/cms/templates/emails/activation_email.txt b/cms/templates/emails/activation_email.txt
new file mode 100644
index 0000000000000000000000000000000000000000..eca5effdd6ebb79f0246f2b8d25bd8cd68c0f50b
--- /dev/null
+++ b/cms/templates/emails/activation_email.txt
@@ -0,0 +1,14 @@
+Someone, hopefully you, signed up for an account for edX's on-line
+offering of "${ course_title}" using this email address. If it was
+you, and you'd like to activate and use your account, copy and paste
+this address into your web browser's address bar:
+
+% if is_secure:
+  https://${ site }/activate/${ key }
+% else:
+  http://edx4edx.mitx.mit.edu/activate/${ key }
+% endif
+
+If you didn't request this, you don't need to do anything; you won't
+receive any more email from us. Please do not reply to this e-mail; if
+you require assistance, check the help section of the edX web site.
diff --git a/cms/templates/emails/activation_email_subject.txt b/cms/templates/emails/activation_email_subject.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c25c006a817d4a299a34a1e2b21ba70906de52f0
--- /dev/null
+++ b/cms/templates/emails/activation_email_subject.txt
@@ -0,0 +1 @@
+Your account for edX's on-line ${course_title} course
diff --git a/cms/templates/marketing.html b/cms/templates/marketing.html
new file mode 100644
index 0000000000000000000000000000000000000000..8731b41e15a86200c6653dba0d49d1b6104b2a99
--- /dev/null
+++ b/cms/templates/marketing.html
@@ -0,0 +1 @@
+<%inherit file="base.html" />
\ No newline at end of file
diff --git a/cms/templates/signup.html b/cms/templates/signup.html
new file mode 100644
index 0000000000000000000000000000000000000000..d3eedc8070863f7cdad4b556b90da42fb69f23b1
--- /dev/null
+++ b/cms/templates/signup.html
@@ -0,0 +1,88 @@
+<%inherit file="base.html" />
+<%block name="title">Sign up</%block>
+
+<%block name="content">
+<section class="main-container">
+
+  <section class="main-content">
+    <header>
+      <h3>Sign Up for edX</h3>
+      <hr>
+    </header>
+
+    <div id="enroll">
+
+      <form id="enroll_form" method="post">
+        <div id="enroll_error" name="enroll_error"></div>
+        <label>E-mail</label>
+        <input name="email" type="email" placeholder="E-mail">
+        <label>Password</label>
+        <input name="password" type="password" placeholder="Password">
+        <label>Public Username</label>
+        <input name="username" type="text" placeholder="Public Username">
+        <label>Full Name</label>
+        <input name="name" type="text" placeholder="Full Name">
+        <label>Your Location</label>
+        <input name="location" type="text" placeholder="Your Location">
+        <label>Preferred Language</label>
+        <input name="language" type="text" placeholder="Preferred Language">
+        <label class="terms-of-service">
+          <input name="terms_of_service" type="checkbox" value="true">
+          I agree to the
+          <a href="#">Terms of Service</a>
+        </label>
+        
+        <!-- no honor code for CMS, but need it because we're using the lms student object -->
+          <input name="honor_code" type="checkbox" value="true" checked="true" hidden="true">
+
+        <div class="submit">
+          <input name="submit" type="submit" value="Create My Account">
+        </div>
+        </form>
+        
+        <section class="login-extra">
+        <p>
+        <span>Already have an account? <a href="#">Login.</a></span>
+        </p>
+        </section>
+
+    </div>
+
+<script type="text/javascript">
+  (function() {
+    function getCookie(name) {
+      return $.cookie(name);
+    }
+
+    function postJSON(url, data, callback) {
+      $.ajax({type:'POST',
+        url: url,
+        dataType: 'json',
+        data: data,
+        success: callback,
+        headers : {'X-CSRFToken':getCookie('csrftoken')}
+      });
+    }
+
+    $('form#enroll_form').submit(function(e) {
+      e.preventDefault();
+      var submit_data = $('#enroll_form').serialize();
+
+      postJSON('/create_account',
+        submit_data,
+        function(json) {
+          if(json.success) {
+            $('#enroll').html(json.value);
+          } else {
+            $('#enroll_error').html(json.value).stop().css("background-color", "#933").animate({ backgroundColor: "#333"}, 2000);
+          }
+        }
+      );
+    });
+  })(this)
+</script>
+
+  </section>
+
+</section>
+</%block>
diff --git a/cms/urls.py b/cms/urls.py
index eb925a7069bf69a349850ef7b7fdff9763d00cfd..dc86535decf1c7ee0524c2ded9637d188ad0dccb 100644
--- a/cms/urls.py
+++ b/cms/urls.py
@@ -1,6 +1,8 @@
 from django.conf import settings
 from django.conf.urls.defaults import patterns, include, url
 
+import django.contrib.auth.views
+
 # Uncomment the next two lines to enable the admin:
 # from django.contrib import admin
 # admin.autodiscover()
@@ -13,6 +15,14 @@ urlpatterns = ('',
     url(r'^github_service_hook$', 'github_sync.views.github_post_receive'),
 )
 
+# User creation and updating views
+urlpatterns += (
+    url(r'^signup$', 'contentstore.views.signup'),
+
+    url(r'^create_account$', 'student.views.create_account'),
+    url(r'^activate/(?P<key>[^/]*)$', 'student.views.activate_account'),
+    )
+
 if settings.DEBUG:
     ## Jasmine
     urlpatterns=urlpatterns + (url(r'^_jasmine/', include('django_jasmine.urls')),)
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index ae64549f308ad4721c5ee805d0c80c55ac0d063b..59692f91bae16ac54acc39c395b51ebb7b88786c 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -143,15 +143,15 @@ def create_account(request, post_override=None):
     # Confirm we have a properly formed request
     for a in ['username', 'email', 'password', 'location', 'language', 'name']:
         if a not in post_vars:
-            js['value']="Error (401 {field}). E-mail us.".format(field=a)
+            js['value'] = "Error (401 {field}). E-mail us.".format(field=a)
             return HttpResponse(json.dumps(js))
 
-    if post_vars['honor_code']!=u'true':
+    if 'honor_code' not in post_vars or post_vars['honor_code'] != u'true':
         js['value']="To enroll, you must follow the honor code.".format(field=a)
         return HttpResponse(json.dumps(js))
 
 
-    if post_vars['terms_of_service']!=u'true':
+    if 'terms_of_service' not in post_vars or post_vars['terms_of_service'] != u'true':
         js['value']="You must accept the terms of service.".format(field=a)
         return HttpResponse(json.dumps(js))
 
@@ -161,7 +161,7 @@ def create_account(request, post_override=None):
     # this is a good idea
     # TODO: Check password is sane
     for a in ['username', 'email', 'name', 'password', 'terms_of_service', 'honor_code']:
-        if len(post_vars[a])<2:
+        if len(post_vars[a]) < 2:
             error_str = {'username' : 'Username of length 2 or greater',
                          'email' : 'Properly formatted e-mail',
                          'name' : 'Your legal name ',
@@ -183,25 +183,23 @@ def create_account(request, post_override=None):
         js['value']="Username should only consist of A-Z and 0-9.".format(field=a)
         return HttpResponse(json.dumps(js))
 
-
-
-    u=User(username=post_vars['username'],
-           email=post_vars['email'],
-           is_active=False)
+    u = User(username=post_vars['username'],
+             email=post_vars['email'],
+             is_active=False)
     u.set_password(post_vars['password'])
-    r=Registration()
+    r = Registration()
     # TODO: Rearrange so that if part of the process fails, the whole process fails.
     # Right now, we can have e.g. no registration e-mail sent out and a zombie account
     try:
         u.save()
     except IntegrityError:
         # Figure out the cause of the integrity error
-        if len(User.objects.filter(username=post_vars['username']))>0:
-            js['value']="An account with this username already exists."
+        if len(User.objects.filter(username=post_vars['username'])) > 0:
+            js['value'] = "An account with this username already exists."
             return HttpResponse(json.dumps(js))
 
-        if len(User.objects.filter(email=post_vars['email']))>0:
-            js['value']="An account with this e-mail already exists."
+        if len(User.objects.filter(email=post_vars['email'])) > 0:
+            js['value'] = "An account with this e-mail already exists."
             return HttpResponse(json.dumps(js))
 
         raise
@@ -209,36 +207,37 @@ def create_account(request, post_override=None):
     r.register(u)
 
     up = UserProfile(user=u)
-    up.name=post_vars['name']
-    up.language=post_vars['language']
-    up.location=post_vars['location']
+    up.name = post_vars['name']
+    up.language = post_vars['language']
+    up.location = post_vars['location']
     up.save()
 
-    d={'name':post_vars['name'],
-       'key':r.activation_key,
-       'course_title' : settings.COURSE_TITLE,
-       }
+    # TODO (vshnayder): the LMS should probably allow signups without a particular course too
+    d = {'name': post_vars['name'],
+         'key': r.activation_key,
+         'course_title': getattr(settings, 'COURSE_TITLE', ''),
+         }
 
-    subject = render_to_string('emails/activation_email_subject.txt',d)
+    subject = render_to_string('emails/activation_email_subject.txt', d)
         # Email subject *must not* contain newlines
     subject = ''.join(subject.splitlines())
-    message = render_to_string('emails/activation_email.txt',d)
+    message = render_to_string('emails/activation_email.txt', d)
 
     try:
         if settings.MITX_FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
             dest_addr = settings.MITX_FEATURES['REROUTE_ACTIVATION_EMAIL']
-            message = "Activation for %s (%s): %s\n" % (u,u.email,up.name) + '-'*80 + '\n\n' + message
+            message = "Activation for %s (%s): %s\n" % (u,u.email,up.name) + '-' * 80 + '\n\n' + message
             send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
         elif not settings.GENERATE_RANDOM_USER_CREDENTIALS:
-            res=u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
+            res = u.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
     except:
         log.exception(sys.exc_info())
-        js['value']='Could not send activation e-mail.'
+        js['value'] = 'Could not send activation e-mail.'
         return HttpResponse(json.dumps(js))
 
-    js={'success':True,
-        'value':render_to_string('registration/reg_complete.html', {'email':post_vars['email'],
-                                                                    'csrf':csrf(request)['csrf_token']})}
+    js={'success': True,
+        'value': render_to_string('registration/reg_complete.html', {'email': post_vars['email'],
+                                                                     'csrf': csrf(request)['csrf_token']})}
     return HttpResponse(json.dumps(js), mimetype="application/json")
 
 def create_random_account(create_account_function):