diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml
index 0a35ad6c394192f60be65549c22d42d526445d86..8364967f428a972f289bea9e19a4e1862250b245 100644
--- a/config/locales/javascript/javascript.en.yml
+++ b/config/locales/javascript/javascript.en.yml
@@ -83,7 +83,9 @@ en:
         reshare: "Reshare"
         comment: "Comment"
         original_post_deleted: "Original post deleted by author."
-        show_post: "Show post"
+        show_nsfw_post: "Show post"
+        show_nsfw_posts: "Show all"
+        hide_nsfw_posts: "Hide #nsfw posts"
         follow: "Follow"
         unfollow: "Unfollow"
 
diff --git a/features/not_safe_for_work.feature b/features/not_safe_for_work.feature
index 3c34c78f183dea684b85319b02a3fc2979b1b6f1..7f7604d74d7895249780c6aac67c50270047ceb4 100644
--- a/features/not_safe_for_work.feature
+++ b/features/not_safe_for_work.feature
@@ -14,14 +14,30 @@ Scenario: Setting not safe for work
   And I submit the form
   Then I should see the "you are safe for work" message
 
-Scenario: NSFWs users posts are nsfw
+Scenario: Toggling nsfw state
+  #Nsfw users posts are marked nsfw
   Given a nsfw user with email "tommy@pr0nking.com"
-  And I sign in as "tommy@pr0nking.com"
+  And a user with email "laura@officeworkers.com"
+  And a user with email "laura@officeworkers.com" is connected with "tommy@pr0nking.com"
+  When I sign in as "tommy@pr0nking.com"
+  And I post "I love 0bj3ction4bl3 c0nt3nt!"
+  And I post "Sexy Senators Gone Wild!"
+  Then I should have 2 nsfw posts
+
+  #toggling global nsfw state
+  When I log out
+  And I sign in as "laura@officeworkers.com"
   Then I should not see "I love 0bj3ction4bl3 c0nt3nt!"
-  #And I post "I love 0bj3ction4bl3 c0nt3nt!"
-  #Then the post "I love 0bj3ction4bl3 c0nt3nt!" should be marked nsfw
+  When I toggle nsfw posts
+  Then I should see "I love 0bj3ction4bl3 c0nt3nt!"
+  And I should see "Sexy Senators Gone Wild!"
+
+  #cookies
+  #When I refresh the page
+  #Then I should see "I love 0bj3ction4bl3 c0nt3nt!"
+  #And I should see "Sexy Senators Gone Wild!"
 
-#  And I log out
-#  And I log in as an office worker
-#  And I am folllowing "tommy@pr0n.xxx"
-#  Then I should not see "I love 0bj3ction4bl3 c0nt3nt!" in my stream
+  #hiding
+  When I toggle nsfw posts
+  Then I should not see "I love 0bj3ction4bl3 c0nt3nt!"
+  And I should not see "Sexy Senators Gone Wild!"
diff --git a/features/step_definitions/session_steps.rb b/features/step_definitions/session_steps.rb
index 360d2637c13fec43c55b1c64e2c65fad6a4008fd..5d5e16ac49a4cff5776893f2e6fbb15ad1143d8c 100644
--- a/features/step_definitions/session_steps.rb
+++ b/features/step_definitions/session_steps.rb
@@ -21,13 +21,13 @@ When /^I try to sign in manually$/ do
   step %(I press "Sign in")
 end
 
-When /^I sign in as "([^"]*)"$/ do |email|
+When /^I (?:sign|log) in as "([^"]*)"$/ do |email|
   @me = User.find_by_email(email)
   @me.password ||= 'password'
   step 'I am signed in'
 end
 
-When /^I sign in with password "([^"]*)"$/ do |password|
+When /^I (?:sign|log) in with password "([^"]*)"$/ do |password|
   @me.password = password
   step 'I am signed in'
 end
diff --git a/features/step_definitions/stream_steps.rb b/features/step_definitions/stream_steps.rb
index f26aa922fdd3eb9efdcca03e4aab38300da2f821..d508ebc58ccd9d773a6c5fec4100a95ed5a4259e 100644
--- a/features/step_definitions/stream_steps.rb
+++ b/features/step_definitions/stream_steps.rb
@@ -9,3 +9,11 @@ end
 Then /^"([^"]*)" should be post (\d+)$/ do |post_text, position|
   find(".stream_element:nth-child(#{position}) .post-content").text.should == post_text
 end
+
+When /^I toggle nsfw posts$/ do
+  find(".toggle_nsfw_state").click
+end
+
+Then /^I should have (\d+) nsfw posts$/ do |num_posts|
+  all(".nsfw-shield").size.should == num_posts.to_i
+end
\ No newline at end of file
diff --git a/public/javascripts/app/app.js b/public/javascripts/app/app.js
index fdb4bf0c5e49d4c86b535285a72a0d72641790a4..ef0e1d13df57bf886630eb7685499d5365a3eae0 100644
--- a/public/javascripts/app/app.js
+++ b/public/javascripts/app/app.js
@@ -4,8 +4,8 @@ var app = {
   helpers: {},
   views: {},
 
-  user: function(user) {
-    if(user) { return this._user = user }
+  user: function(userAttrs) {
+    if(userAttrs) { return this._user = new app.models.User(userAttrs) }
     return this._user || false
   },
 
diff --git a/public/javascripts/app/models/user.js b/public/javascripts/app/models/user.js
new file mode 100644
index 0000000000000000000000000000000000000000..247346b08068514820cb92a7b78bbff917cfbc65
--- /dev/null
+++ b/public/javascripts/app/models/user.js
@@ -0,0 +1,6 @@
+app.models.User = Backbone.Model.extend({
+  toggleNsfwState : function() {
+    this.set({showNsfw : !this.get("showNsfw")});
+    this.trigger("nsfwChanged");
+  }
+});
diff --git a/public/javascripts/app/templates/stream-element.handlebars b/public/javascripts/app/templates/stream-element.handlebars
index 7af3c8872a89ec3d346e0898bd90a5dca52fd5e6..47b59963d3bfc9b3bbe90dd7605bcff3d7211b15 100644
--- a/public/javascripts/app/templates/stream-element.handlebars
+++ b/public/javascripts/app/templates/stream-element.handlebars
@@ -44,24 +44,37 @@
       </span>
     </div>
 
-    {{#if nsfw}}
+    {{#if showPost}}
+        {{#if nsfw}}
+          <div class="nsfw_off">
+            <strong>
+              #NSFW
+            </strong>
+            |
+            <a href="#" class="toggle_nsfw_state">
+                {{t "stream.hide_nsfw_posts"}}
+            </a>
+          </div>
+        {{/if}}
+      <div class="post-content"> </div>
+
+      <div class="feedback"> </div>
+      <div class="likes"> </div>
+      <div class="comments"> </div>
+    {{else}}
       <div class="nsfw-shield">
         <strong>
-          NSFW
+          #NSFW
         </strong>
         |
-        <a href="#">
-          {{t "stream.show_post"}}
+        <a href="#" class="show_nsfw_post">
+          {{t "stream.show_nsfw_post"}}
+        </a>
+        |
+        <a href="#" class="toggle_nsfw_state">
+          {{t "stream.show_nsfw_posts"}}
         </a>
       </div>
-
-    {{else}}
-
-      <div class="post-content"> </div>
-
-      <div class="feedback"> </div>
-      <div class="likes"> </div>
-      <div class="comments"> </div>
     {{/if}}
 
   </div>
diff --git a/public/javascripts/app/views.js b/public/javascripts/app/views.js
index b55c6a04152e80755a5462c6b2575bfce611b593..9b15044c164a4c48c65b2d44c8f6de13ce0ba742 100644
--- a/public/javascripts/app/views.js
+++ b/public/javascripts/app/views.js
@@ -11,7 +11,7 @@ app.views.Base =  Backbone.View.extend({
 
   defaultPresenter : function(){
     var modelJson = this.model ? this.model.toJSON() : {}
-    return _.extend(modelJson, {current_user: app.user()});
+    return _.extend(modelJson, {current_user: app.user().attributes});
   },
 
   render : function() {
diff --git a/public/javascripts/app/views/comment_view.js b/public/javascripts/app/views/comment_view.js
index 9ef1307a46efb4456f63d7f2be47e03e72a56da8..8c36dfa1c83c176e039dc01e5c16acae3f65ed4c 100644
--- a/public/javascripts/app/views/comment_view.js
+++ b/public/javascripts/app/views/comment_view.js
@@ -16,11 +16,11 @@ app.views.Comment = app.views.Content.extend({
   },
 
   ownComment : function() {
-    return this.model.get("author").diaspora_id == app.user().diaspora_id
+    return this.model.get("author").diaspora_id == app.user().get("diaspora_id")
   },
 
   postOwner : function() {
-    return this.model.get("parent").author.diaspora_id == app.user().diaspora_id
+    return this.model.get("parent").author.diaspora_id == app.user().get("diaspora_id")
   },
 
   canRemove : function() {
diff --git a/public/javascripts/app/views/feedback_view.js b/public/javascripts/app/views/feedback_view.js
index 4543680bb0b8c6126e2e27a9cd08c919ca541e64..ab967961940c69f3f05ffb6596de460b39a67d64 100644
--- a/public/javascripts/app/views/feedback_view.js
+++ b/public/javascripts/app/views/feedback_view.js
@@ -43,8 +43,8 @@ app.views.Feedback = app.views.StreamObject.extend({
     var rootExists = (isReshare ? this.model.get("root") : true)
 
     var publicPost = this.model.get("public");
-    var userIsNotAuthor = this.model.get("author").diaspora_id != app.user().diaspora_id;
-    var userIsNotRootAuthor = rootExists && (isReshare ? this.model.get("root").author.diaspora_id != app.user().diaspora_id : true)
+    var userIsNotAuthor = this.model.get("author").diaspora_id != app.user().get("diaspora_id");
+    var userIsNotRootAuthor = rootExists && (isReshare ? this.model.get("root").author.diaspora_id != app.user().get("diaspora_id") : true)
 
     return publicPost && userIsNotAuthor && userIsNotRootAuthor;
   }
diff --git a/public/javascripts/app/views/post_view.js b/public/javascripts/app/views/post_view.js
index 9d93b28ba45e41e39c31f6b38858a3d31c5a48cd..31307e79ba617179154271365f3b94f16cd52e8c 100644
--- a/public/javascripts/app/views/post_view.js
+++ b/public/javascripts/app/views/post_view.js
@@ -6,7 +6,9 @@ app.views.Post = app.views.StreamObject.extend({
 
   events: {
     "click .focus_comment_textarea": "focusCommentTextarea",
-    "click .nsfw-shield a": "removeNsfwShield",
+    "click .show_nsfw_post": "removeNsfwShield",
+    "click .toggle_nsfw_state": "toggleNsfwState",
+
     "click .remove_post": "destroyModel",
     "click .hide_post": "hidePost",
     "click .block_user": "blockUser"
@@ -49,15 +51,24 @@ app.views.Post = app.views.StreamObject.extend({
 
   presenter : function() {
     return _.extend(this.defaultPresenter(), {
-      authorIsCurrentUser : this.authorIsCurrentUser()
+      authorIsCurrentUser : this.authorIsCurrentUser(),
+      showPost : this.showPost()
     })
   },
 
+  showPost : function() {
+    return (app.user() && app.user().get("showNsfw")) || !this.model.get("nsfw")
+  },
+
   removeNsfwShield: function(evt){
     if(evt){ evt.preventDefault(); }
     this.model.set({nsfw : false})
     this.render();
-    return this;
+  },
+
+  toggleNsfwState: function(evt){
+    if(evt){ evt.preventDefault(); }
+    app.user().toggleNsfwState();
   },
 
   blockUser: function(evt){
diff --git a/public/javascripts/app/views/stream_view.js b/public/javascripts/app/views/stream_view.js
index 20e9566aced7d981b8b7e5616804b425aa8b88c7..be37cd134da7260bf7528aed9ba1543bfeba0e4f 100644
--- a/public/javascripts/app/views/stream_view.js
+++ b/public/javascripts/app/views/stream_view.js
@@ -11,12 +11,16 @@ app.views.Stream = Backbone.View.extend({
     this.setupEvents()
     this.setupInfiniteScroll()
     this.setupLightbox()
+    this.postViews = []
   },
 
   setupEvents : function(){
     this.stream.bind("fetched", this.removeLoader, this)
     this.stream.bind("allPostsLoaded", this.unbindInfScroll, this)
     this.collection.bind("add", this.addPost, this);
+    app.user().bind("nsfwChanged", function() {
+        _.map(this.postViews, function(view){ view.render() })
+      }, this)
   },
 
   addPost : function(post) {
@@ -28,6 +32,7 @@ app.views.Stream = Backbone.View.extend({
         : "append"
     ](postView.render().el);
 
+    this.postViews.push(postView)
     return this;
   },
 
diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass
index d6a425d807d3a8f22f935d9f8181c299c17f27cf..1ee9621e978a83bd95d1582e3c3332032e1807ce 100644
--- a/public/stylesheets/sass/application.sass
+++ b/public/stylesheets/sass/application.sass
@@ -2815,3 +2815,9 @@ a.toggle_selector
 .float-right
   :float right
   :margin-top 5px
+
+.nsfw_off
+  :font-size smaller
+  :color #999
+  a
+    :color #666
\ No newline at end of file
diff --git a/spec/javascripts/app/app_spec.js b/spec/javascripts/app/app_spec.js
index 0be9ee9753d9a5bb381c17f0c847525f6071bec8..eaf1a90a7e0f917401ce8ae72f727568930ba8c6 100644
--- a/spec/javascripts/app/app_spec.js
+++ b/spec/javascripts/app/app_spec.js
@@ -5,7 +5,7 @@ describe("app", function() {
 
       app.user({name: "alice"});
 
-      expect(app.user()).toEqual({name: "alice"});
+      expect(app.user().get("name")).toEqual("alice");
     });
     
     it("returns false if the current_user isn't set", function() {
diff --git a/spec/javascripts/app/views/post_view_spec.js b/spec/javascripts/app/views/post_view_spec.js
index 2f52e44a62465b9486f719ce707e86398cafd98d..4ecb335691591c55f006bde4f962d3dcea02b4ae 100644
--- a/spec/javascripts/app/views/post_view_spec.js
+++ b/spec/javascripts/app/views/post_view_spec.js
@@ -61,23 +61,40 @@ describe("app.views.Post", function(){
     })
 
     context("NSFW", function(){
-      it("contains a shield element", function(){
+      beforeEach(function(){
         this.statusMessage.set({nsfw: true});
+        this.view = new app.views.Post({model : this.statusMessage}).render();
 
-        var view = new app.views.Post({model : this.statusMessage}).render();
-        var statusElement = $(view.el)
+        this.hiddenPosts = function(){
+           return this.view.$(".nsfw-shield")
+         }
+      });
 
-        expect(statusElement.find(".nsfw-shield").length).toBe(1)
-      })
+      it("contains a shield element", function(){
+        expect(this.hiddenPosts().length).toBe(1)
+      });
 
-      it("does not contain a shield element", function(){
+      it("does not contain a shield element when nsfw is false", function(){
         this.statusMessage.set({nsfw: false});
+        this.view.render();
+        expect(this.hiddenPosts()).not.toExist();
+      })
 
-        var view = new app.views.Post({model : this.statusMessage}).render();
-        var statusElement = $(view.el)
-
-        expect(statusElement.find(".shield").html()).toBe(null);
+      context("showing a single post", function(){
+        it("removes the shields when the post is clicked", function(){
+          expect(this.hiddenPosts()).toExist();
+          this.view.$(".nsfw-shield .show_nsfw_post").click();
+          expect(this.hiddenPosts()).not.toExist();
+        });
+      });
+
+      context("clicking the toggle nsfw link toggles it on the user", function(){
+        it("calls toggleNsfw on the user", function(){
+          spyOn(app.user(), "toggleNsfwState")
+          this.view.$(".toggle_nsfw_state").first().click();
+          expect(app.user().toggleNsfwState).toHaveBeenCalled();
+        });
       })
     })
   })
-})
+});
diff --git a/spec/javascripts/helpers/factory.js b/spec/javascripts/helpers/factory.js
index b0e871911384e3c843b484459fb6336f2bb70a97..c5c0520cde202877c06fc753b85dfa5638a3f4f0 100644
--- a/spec/javascripts/helpers/factory.js
+++ b/spec/javascripts/helpers/factory.js
@@ -35,15 +35,15 @@ factory = {
 
   userAttrs : function(overrides){
     var id = this.id.next()
-      var defaultAttrs = {
-        "name":"Awesome User" + id,
-        "id": id,
-        "diaspora_id": "bob@bob.com",
-        "avatar":{
-          "large":"http://localhost:3000/images/user/uma.jpg",
-          "medium":"http://localhost:3000/images/user/uma.jpg",
-          "small":"http://localhost:3000/images/user/uma.jpg"}
-      }
+    var defaultAttrs = {
+      "name":"Awesome User" + id,
+      "id": id,
+      "diaspora_id": "bob@bob.com",
+      "avatar":{
+        "large":"http://localhost:3000/images/user/uma.jpg",
+        "medium":"http://localhost:3000/images/user/uma.jpg",
+        "small":"http://localhost:3000/images/user/uma.jpg"}
+    }
 
     return _.extend(defaultAttrs, overrides)
   },