{"action":"created","comment":{"url":"https://api.github.com/repos/wework/wework.github.io/pulls/comments/47800085","id":47800085,"diff_hunk":"@@ -0,0 +1,733 @@\n+---\n+layout:       post\n+title:        Simply Secure Rails API\n+author:       paul_franzen\n+summary:      If you're attempting to implement a service orientated architecture one of the most daunting tasks can be deciding on a method for securing API endpoints.\n+image:        http://res.cloudinary.com/wework/image/upload/s--Al86WVSb--/c_fill,fl_progressive,g_face:center,h_1000,q_jpegmini:1,w_1600/v1427921778/engineering/5-ways-to-create-bulletproof-software-bear.jpg\n+categories:   rails, api, security, engineering\n+---\n+\n+**Quick Note:** \n+\n+> Hi, you probably have not seen a lot of articles/posts about how organizations secure their content on the Interwebs. I'm guessing this is a symptom of: \n+> \n+> 1. Fear of exposing specific practices that could put their data / applications at risk.\n+> \n+> 2. Fear of backlash among the development community where misunderstandings and semantic arguments reign supreme. And can label an entire company, or an individual (myself) as incompetent.\n+> \n+> I'd by lying if I said that these fears are not in my mind when authoring this post, but by relegating these topics to back-room-locked-down-hundred-page-decks from security consultants we deny ourselves public and accessible conversations around one of the most important issues in our industry. \n+> \n+> Working at a company like WeWork, I (We) believe that individual success is exponentially more achievable when you're part of a greater community. \n+> \n+> This belief can sometimes mean uncomfortable travels in which trust outweighs mistrust and you are willing to take on risk in the interest of greater success for both yourself and your community.\n+> \n+> Ok. Now that's out of the way. What follows is a system/explanation, undoubtedly flawed, but one which We hope will help some folks to understand some security concepts a little better and maybe help them in there travels. \n+> \n+> Thank you, and as always, comments and feedback are not only welcome, but necessary.\n+\n+\n+\n+---\n+\n+\n+\n+If you're attempting to implement a service orientated architecture \n+one of the most daunting tasks can be deciding on a method for \n+securing API endpoints.\n+\n+A simple (and obvious) solution is to make every request using an \n+API key or something like [Basic HTTP Authentication headers](https://en.wikipedia.org/wiki/Basic_access_authentication).\n+\n+Something like:\n+\n+Requesting APP (using the wonderful [Faraday](https://github.com/lostisland/faraday)):\n+\n+```ruby\n+  Faraday.new(:url => \"https://www.api.io/api/v1/posts\") do |faraday|\n+    faraday.adapter Faraday.default_adapter\n+    faraday.use Faraday::Request::TokenAuthentication,\n+    faraday.headers['Content-Type'] = 'application/json'\n+    faraday.token_auth(\"1234567cy\")\n+  end\n+```\n+\n+Receiving APP:\n+\n+```ruby\n+  module Api\n+    module V1\n+      class PostsController < ApplicationController\n+        before_filter :restrict_access\n+\n+        def index\n+          @posts = Post.page(1)\n+          respond_with @posts\n+        end\n+\n+        private\n+\n+        def restrict_access        \n+          authenticate_or_request_with_http_token do |token, options|          \n+            ApiKey.exists?(access_token: token)\n+          end\n+        end\n+\n+      end\n+    end\n+  end\n+```\n+\n+This is a fine system, pleasingly simple and straight-forward.\n+However, it misses one basic fact about these requests: Most \n+are made on behalf of the current **user and not the requesting\n+application.** \n+\n+So a posts controller:\n+\n+```ruby\n+  module Api\n+    module V1\n+      class PostsController < ApplicationController\n+        before_filter :restrict_access\n+\n+        def index\n+          @posts = current_user.posts.page(1)\n+          respond_with @posts\n+        end\n+\n+        private\n+\n+        def restrict_access        \n+          authenticate_or_request_with_http_token do |token, options|          \n+            ApiKey.exists?(access_token: token)\n+          end\n+        end\n+\n+      end\n+    end\n+  end\n+```\n+\n+Is, well, a little awkward. Because there's really no way to \n+know who the \"current_user\" is without passing in some extra variable.\n+\n+\n+#### Enter OAuth.\n+\n+OAuth especially via [DoorKeeper](https://github.com/doorkeeper-gem/doorkeeper) \n+and [Omniauth](https://github.com/intridea/omniauth), is amazing, \n+but popping-open new windows (or mobile apps), redirect URIs, tokens, secrets, and the funky \n+user experience that often accompany them can be a bit too much\n+ceremony especially if you're connecting two *trusted* applications.\n+\n+#### Tokens to the rescue!\n+\n+Encrypting strings that can be later decrypted (usually obfuscated to a word: Token)\n+is a concept oft hidden by Wizard-Of-Oz style curtains within frameworks like Rails. \n+\n+Just set some random ```secret_base_key``` and you're good to go!\n+\n+When you dig in, oh boy, *Initialization Vectors*, *Ciphers*, wacky words / bad hacker nicknames, inspiring both dread and awe in the uninitiated. \n+But just like any good magic trick deciphering the slight of hand is both empowering and inspiring. \n+\n+So let's take a peek behind the curtain.\n+\n+#### Imagine a scenario\n+\n+##### Application 1: \n+In a more traditional architecture, our Authentication service (id.wework.io for example) would be the OAuth provider.\n+\n+This service has a login API. For the sake of brevity, it uses [Devise](https://github.com/plataformatec/devise).\n+This application also utilizes CORS via [Rack CORs](https://github.com/cyu/rack-cors) to allow appropriately configured cross-origin access.\n+\n+##### It has a user model\n+\n+```ruby\n+class User < ActiveRecord::Base\n+  # Schema\n+  # Name: string\n+  # UUID: UUID (primary key)\n+  # Email\n+  # Devise password stuff\n+  devise :database_authenticatable\n+end\n+```\n+\n+##### It has a base API controller\n+\n+```ruby\n+module Api\n+  class ApiController < ApplicationController  \n+    skip_before_action :verify_authenticity_token\n+  end\n+end\n+```\n+\n+##### It has a login controller endpoint:\n+\n+```ruby\n+module Api\n+  module V1\n+    class LoginController < ApiController\n+      \n+      def create # POST: /api/v1/login\n+        @user = User.find_by(email: params[:email])\n+\n+        if User.authenticate(params[:email], params[:password])\n+          respond_with: @user.as_json()\n+        else \n+          head 404\n+        end\n+      end\n+\n+    end\n+  end\n+end\n+```\n+\n+##### It has a users controller\n+\n+```ruby\n+module Api\n+  module V1\n+    class UsersController < ApiController\n+      \n+      def show # GET: /api/v1/users/[UUID]\n+        @user = User.find_by(uuid: params[:id])\n+\n+        if @user.present?\n+          respond_with: @user.as_json()\n+        else \n+          head 404\n+        end\n+      end\n+\n+    end\n+  end\n+end\n+```\n+\n+---\n+\n+##### Application 2: \n+Social Network (https://network.wework.io)\n+A rails web application which uses the aforementioned authentication service to log a user in and seed user data.\n+\n+We want this application to log a user in, persist a \"copy\" of this user (like any standard OAuth app) and allow that user to create posts.\n+\n+#### Let's get started\n+\n+First you want to add a method to the authentication service's User Model. This method encrypts a\n+user's uuid.\n+\n+Using ruby's OpenSSL library\n+\n+```ruby\n+class User < ActiveRecord::Base\n+  # Schema\n+  # name: string\n+  # uuid: UUID (primary key)\n+  # email: string\n+  # Devise password stuff\n+  devise :database_authenticatable\n+\n+  def encrypted\n+    srting_to_encrypt = \"#{self.uuid}\"\n+    cipher         =  OpenSSL::Cipher.new('aes-256-cbc')\n+    cipher.encrypt\n+    cipher.key     =  ENV['SOME_SECRET_KEY']\n+    cipher.iv      =  ENV['SOME_SECRET_IV']\n+    cipher.padding =  1\n+    encrypted      =  cipher.update(self.uuid) + cipher.final # use only this user's UUID for encryption\n+    Base64.urlsafe_encode64(encrypted).encode('utf-8')\n+  end\n+end\n+```\n+\n+Look at this bonkers encrypted method! It's pretty hard to figure out what's going on here. And to be fair, it's way outside \n+the norm of ordinary, MVC style web development. So lets go through it line by line.\n+\n+#### Line 1\n+```ruby\n+cipher =  OpenSSL::Cipher.new('aes-256-cbc')\n+```\n+\n+The argument 'aes-256-cbc' is a hyphenated description of components of the cipher. \n+\n+1. \"aes\" is the name of the encryption algorithm. In this case, its an acronym for [Advanced Encryption Standard](http://aesencryption.net/).\n+\n+2. 256 is the number of bits of the key used in the algorithm. You'll usually see 128 or 256. Practically, this means that the key is \"large\" enough that it cannot be feasibly broken via a brute force attack (it would take foooooorrreeeevvvveeeerrr) . \n+\n+3. cbc stands for \"cipher block chaining\" and is the mode by which the aes algorithm encrypts the data. Its most commonly compared to the ecb [electronic code book](http://searchsecurity.techtarget.com/definition/Electronic-Code-Book). \n+Its generally preferred to use cbc because ebc may expose pieces of the actual key in the encrypted string when encrypting smaller pieces of data. \n+\n+More in-depth documentation is [available here](http://docs.ruby-lang.org/en/trunk/OpenSSL/Cipher.html). \n+\n+---\n+\n+#### Line 2\n+```ruby\n+cipher.encrypt # cipher.decrypt\n+```\n+\n+Since you instantiate a \"decipher\" the same as a cipher, this line lets the instance know that we intend to use it for encryption rather than decryption.\n+\n+---\n+\n+#### Line 3\n+```ruby\n+cipher.key =  ENV['SOME_SECRET_KEY']\n+```\n+\n+OK, here's where it starts to get interesting. \n+This is the first part of the password that we will shared between applications. \n+Its important to create a truly random (or as close to that as possible) key, so consult the [documentation](http://docs.ruby-lang.org/en/trunk/OpenSSL/Cipher.html#class-OpenSSL::Cipher-label-Choosing+a+key)\n+to get something as secure as possible.\n+\n+---\n+\n+#### Line 4\n+```ruby\n+cipher.iv =  ENV['SOME_SECRET_IV']\n+```\n+\n+IV, well this stands for initialization vector. An initialization vector creates an extra layer of security around a key, and is generally used only once per instance of a cipher. \n+We're breaking the rules a little here, because our IV will be used only to prevent the accidental exposure of the key in the encrypted payload. \n+Consult the [documentation](http://docs.ruby-lang.org/en/trunk/OpenSSL/Cipher.html#class-OpenSSL::Cipher-label-Choosing+an+IV) to choose your IV.\n+\n+---\n+\n+For both of these (as implied by the code), you'll want to store them outside of version control and in some sort of easily changeable way in case they are ever compromised.\n+\n+You'll also want to use different values for each environment, i.e. development, test, staging, production.\n+\n+---\n+\n+#### Line 5\n+```ruby\n+cipher.padding =  1\n+```\n+\n+This sets the cipher padding to 1 character. Just kidding! This is a boolean, which ENABLES padding in the cipher.  The short story is that the aes\n+block cipher algorithm mentioned above requires its input to be an EXACT multiple of the block size. Setting this to 1 allows for variance in the size of the data being encrypted. \n+If you're very interested: read the full [documentation](http://ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-padding-3D)\n+\n+---\n+\n+#### Line 6\n+```ruby\n+encrypted =  cipher.update(self.uuid) + cipher.final\n+```\n+This takes the actual data you want to encrypt, in this case the user's \n+UUID, and turns is into something that's actually ready to be deciphered.\n+[Documentation](http://docs.ruby-lang.org/en/trunk/OpenSSL/Cipher.html#method-i-final)\n+\n+---\n+\n+#### Line 7\n+```ruby\n+Base64.urlsafe_encode64(encrypted).encode('utf-8')\n+```\n+This is the actual data we want to transform the finalized cipher into so that it can be passed safely via a query string or form data. Only over SSL, of course :)\n+\n+---\n+\n+After our authentication application's user model has an encrypted method we can modify the: \n+\n+##### Authentication Application's User's controller\n+\n+```ruby\n+module Api\n+  module V1\n+    class LoginController < ApiController\n+      \n+      def create # POST: /api/v1/login\n+        @user = User.find_by(email: params[:email])\n+\n+        if User.authenticate(params[:email], params[:password])\n+          respond_with: @user.as_json(methods: [:encrypted])\n+        else \n+          head 404\n+        end\n+      end\n+\n+    end\n+  end\n+end\n+```\n+\n+Now that our authentication application is prepped to return encrypted UUIDs (when provided a valid email and password), we\n+can create our Social Networking application here's some boilerplate:\n+\n+##### Our Social Network's user class\n+\n+```ruby\n+class User < ActiveRecord::Base\n+  # Schema\n+  # name: string\n+  # uuid: uuid (primary key)\n+  # email: string\n+\n+  def self.create_from_authentication_service(email, password)\n+    begin\n+      connection =  Faraday.new(url: \"https://id.wework.io\") do |faraday|\n+                      faraday.adapter Faraday.default_adapter    \n+                      faraday.headers['Content-Type'] = 'application/json'\n+                    end\n+\n+      response = connection.post(\"/api/v1/login/\", {\n+                    email: email,\n+                    password: password\n+                  }.to_json)\n+\n+      parsed_body = JSON.parse(response.body)\n+\n+      User.create({\n+        uuid:  parsed_body[\"user\"][\"uuid\"],\n+        email: parsed_body[\"user\"][\"email\"],\n+        name:  parsed_body[\"user\"][\"name\"]\n+      })\n+\n+      parsed_body\n+    rescue \n+      false\n+    end\n+  end\n+end\n+```\n+\n+##### Our Social Network login controller\n+\n+```ruby\n+class LoginController < ApplicationController\n+\n+  def new # Get /login\n+  end\n+\n+  def create  # Post/login\n+\n+    user = User.create_from_authentication_service(params[:email], params[:password])\n+\n+    if user\n+      render json: user\n+    else\n+      redirect new_login_path, error: \"Something went wrong!\"\n+    end\n+  \n+  end\n+\n+end\n+\n+```\n+\n+##### Our new Social Network login view\n+```html\n+<html>\n+  <head>\n+    <title>Network</title>\n+  </head>\n+  <body>\n+    <form method=\"POST\" action=\"<%= create_login_path %>\" id=\"loginForm\">\n+      <input type=\"text\" name=\"email\" placeholder=\"Email\" />\n+      <input type=\"password\" name=\"password\" placeholder=\"password\" />\n+      <input type=\"submit\" value=\"submit\" />\n+    </form>\n+    <script>\n+      $(\"#loginForm\").on(\"submit\", function(){\n+        $.ajax({\n+          url: $(this).attr(\"action\"),\n+          method: \"POST\",\n+          data: $(this).serialize()\n+        })\n+        .success(function(response) {\n+          localStorage.setItem(\"e\", response.encrypted);\n+        });\n+      });\n+    </script>\n+  </body>\n+</html>\n+```\n+---\n+\n+Here's the important part:\n+\n+##### Our new Social Network's Application Controller\n+\n+```ruby\n+class ApplicationController < ActionController::Base\n+\n+  protected\n+\n+  def decrypt(encrypted_uuid)\n+    begin\n+      decipher         = OpenSSL::Cipher.new 'aes-256-cbc'\n+      decipher.decrypt\n+      decipher.padding = 1\n+      decipher.key     = ENV['SOME_SECRET_KEY']\n+      decipher.iv      = ENV['SOME_SECRET_IV']\n+      decrypted        = cipher.update(Base64.urlsafe_decode64(encrypted_uuid)) + decipher.final\n+      decrypted\n+    rescue\n+      head 403 and return\n+    end\n+  end\n+\n+  def current_user\n+    if params[:encrypted_user_uuid]\n+      @current_user ||= User.find_by_uuid( decrypt_uuid(params[:encrypted_user_uuid]) )\n+    end\n+  end\n+  helper_method :current_user\n+\n+  def require_user\n+    if !current_user.present?\n+      head 403\n+    end\n+  end\n+\n+end\n+```\n+\n+You'll notice that the ``` decrypted_uuid ``` follows the same pattern as the encryption method. \n+As you can probably guess, this runs through the previously documented code, essentially in reverse, and it outputs a recognize-able\n+uuid that you can use for lookup.\n+\n+Now our social network can implement a controller like: \n+\n+```ruby\n+class PostsController < ApplicationController\n+  before_filter :require_user\n+\n+  def index\n+    render json: current_user.posts\n+  end\n+\n+end\n+```\n+\n+Which can serve to either a client web app or a mobile app as long as you pass ```encrypted_user_uuid``` as a parameter in each request.\n+\n+\n+### Great Success!\n+\n+---\n+\n+#### Lets Go The Extra Mile\n+\n+This implementation is pretty solid, and satisfies the basic needs for locking requests, but it leaves much room for improvement. \n+\n+Let's look at some of the problems, and try to fix them w/o making everything really complicated.\n+\n+#### Problem 1: Can't revoke these tokens individually\n+\n+By using ENV variables for the IV and Key we can revoke permissions, but only for \"all users\" not individual users. \n+\n+\n+Lets fix this!\n+\n+##### Our modified Social Network's user class\n+\n+```ruby\n+class User < ActiveRecord::Base\n+  # Schema\n+  # name: string\n+  # uuid: uuid (primary key)\n+  # email: string\n+  # token: string\n+  def before_validation :create_token\n+\n+  def create_token\n+    self.token ||= SecureRandom.urlsafe_base64(nil, false)\n+  end\n+\n+  def self.create_from_authentication_service(email, password)\n+    begin\n+      connection =  Faraday.new(url: \"https://id.wework.io\") do |faraday|\n+                      faraday.adapter Faraday.default_adapter    \n+                      faraday.headers['Content-Type'] = 'application/json'\n+                    end\n+\n+      response = connection.post(\"/api/v1/login/\", {\n+                    email: email,\n+                    password: password\n+                  }.to_json)\n+\n+      parsed_body = JSON.parse(response.body)\n+\n+      user = User.create({\n+        uuid:  parsed_body[\"user\"][\"uuid\"],\n+        email: parsed_body[\"user\"][\"email\"],\n+        name:  parsed_body[\"user\"][\"name\"]\n+      })\n+\n+      {user: user, payload: parsed_body}\n+    rescue \n+      false\n+    end\n+  end\n+end\n+```\n+\n+Here we're creating a random token, per user, which we can alter / delete if the top level ever becomes compromised.\n+\n+\n+##### Our modified Social Network login view\n+```html\n+<html>\n+  <head>\n+    <title>Network</title>\n+  </head>\n+  <body>\n+    <form method=\"POST\" action=\"<%= create_login_path %>\" id=\"loginForm\">\n+      <input type=\"text\" name=\"email\" placeholder=\"Email\" />\n+      <input type=\"password\" name=\"password\" placeholder=\"password\" />\n+      <input type=\"submit\" value=\"submit\" />\n+    </form>\n+    <script>\n+      $(\"#loginForm\").on(\"submit\", function(){\n+        $.ajax({\n+          url: $(this).attr(\"action\"),\n+          method: \"POST\",\n+          data: $(this).serialize()\n+        })\n+        .success(function(response) {\n+          localStorage.setItem(\"e\", response.payload.encrypted);\n+          localStorage.setItem(\"t\", response.user.token);\n+        });\n+      });\n+    </script>\n+  </body>\n+</html>\n+```\n+\n+##### Our new Social Network's Application Controller\n+\n+```ruby\n+class ApplicationController < ActionController::Base\n+\n+  protected\n+\n+  def decrypt(encrypted_uuid)\n+    begin\n+      decipher         = OpenSSL::Cipher.new 'aes-256-cbc'\n+      decipher.decrypt\n+      decipher.padding = 1\n+      decipher.key     = ENV['SOME_SECRET_KEY']\n+      decipher.iv      = ENV['SOME_SECRET_IV']\n+      decrypted        = cipher.update(Base64.urlsafe_decode64(encrypted_uuid)) + decipher.final\n+      decrypted\n+    rescue\n+      head 403 and return\n+    end\n+  end\n+\n+  def current_user\n+    if params[:encrypted_user_uuid] && params[:token]\n+      @current_user ||= User.find_by(uuid: decrypt_uuid(params[:encrypted_user_uuid]), token: params[:token])\n+    end\n+  end\n+  helper_method :current_user\n+\n+  def require_user\n+    if !current_user.present?\n+      head 403\n+    end\n+  end\n+\n+end\n+```\n+\n+Now we are accessing this user based on an easily revoke-able token in the client app.\n+\n+---\n+\n+##### Problem 2: Decoding\n+\n+Since we are encoding a uuid and the encrypted uuid is stored in the client its *technically possible* ([the best kind of possible](https://www.youtube.com/watch?v=hou0lU8WMgo)) for a nefarious person to gather up \n+enough uuids and their encrypted counterparts to break our encryption. \n+\n+So lets add some additional values to our encryption for parsing.","path":"_posts/2015-12-14-simply-secure-rails-api.markdown","position":652,"original_position":649,"commit_id":"7c4cba7fbbbb22771c2ecb63fe038f7c64035997","original_commit_id":"6cd8ec630f767acb587c51f0b7a80e5f859a2b38","user":{"login":"jackbowman","id":3697836,"avatar_url":"https://avatars.githubusercontent.com/u/3697836?v=3","gravatar_id":"","url":"https://api.github.com/users/jackbowman","html_url":"https://github.com/jackbowman","followers_url":"https://api.github.com/users/jackbowman/followers","following_url":"https://api.github.com/users/jackbowman/following{/other_user}","gists_url":"https://api.github.com/users/jackbowman/gists{/gist_id}","starred_url":"https://api.github.com/users/jackbowman/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jackbowman/subscriptions","organizations_url":"https://api.github.com/users/jackbowman/orgs","repos_url":"https://api.github.com/users/jackbowman/repos","events_url":"https://api.github.com/users/jackbowman/events{/privacy}","received_events_url":"https://api.github.com/users/jackbowman/received_events","type":"User","site_admin":false},"body":"let's","created_at":"2015-12-16T16:42:59Z","updated_at":"2015-12-16T16:42:59Z","html_url":"https://github.com/wework/wework.github.io/pull/14#discussion_r47800085","pull_request_url":"https://api.github.com/repos/wework/wework.github.io/pulls/14","_links":{"self":{"href":"https://api.github.com/repos/wework/wework.github.io/pulls/comments/47800085"},"html":{"href":"https://github.com/wework/wework.github.io/pull/14#discussion_r47800085"},"pull_request":{"href":"https://api.github.com/repos/wework/wework.github.io/pulls/14"}}},"pull_request":{"url":"https://api.github.com/repos/wework/wework.github.io/pulls/14","id":53623230,"html_url":"https://github.com/wework/wework.github.io/pull/14","diff_url":"https://github.com/wework/wework.github.io/pull/14.diff","patch_url":"https://github.com/wework/wework.github.io/pull/14.patch","issue_url":"https://api.github.com/repos/wework/wework.github.io/issues/14","number":14,"state":"open","locked":false,"title":"Post/simple part1 simple api lockdown","user":{"login":"paulfranzen","id":451312,"avatar_url":"https://avatars.githubusercontent.com/u/451312?v=3","gravatar_id":"","url":"https://api.github.com/users/paulfranzen","html_url":"https://github.com/paulfranzen","followers_url":"https://api.github.com/users/paulfranzen/followers","following_url":"https://api.github.com/users/paulfranzen/following{/other_user}","gists_url":"https://api.github.com/users/paulfranzen/gists{/gist_id}","starred_url":"https://api.github.com/users/paulfranzen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paulfranzen/subscriptions","organizations_url":"https://api.github.com/users/paulfranzen/orgs","repos_url":"https://api.github.com/users/paulfranzen/repos","events_url":"https://api.github.com/users/paulfranzen/events{/privacy}","received_events_url":"https://api.github.com/users/paulfranzen/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-12-14T21:19:27Z","updated_at":"2015-12-16T16:42:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c174da1702ddbacae28b01d8399d7f8ba0f96d67","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/wework/wework.github.io/pulls/14/commits","review_comments_url":"https://api.github.com/repos/wework/wework.github.io/pulls/14/comments","review_comment_url":"https://api.github.com/repos/wework/wework.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/wework/wework.github.io/issues/14/comments","statuses_url":"https://api.github.com/repos/wework/wework.github.io/statuses/7c4cba7fbbbb22771c2ecb63fe038f7c64035997","head":{"label":"wework:post/simple-part1-simple-api-lockdown","ref":"post/simple-part1-simple-api-lockdown","sha":"7c4cba7fbbbb22771c2ecb63fe038f7c64035997","user":{"login":"wework","id":4072897,"avatar_url":"https://avatars.githubusercontent.com/u/4072897?v=3","gravatar_id":"","url":"https://api.github.com/users/wework","html_url":"https://github.com/wework","followers_url":"https://api.github.com/users/wework/followers","following_url":"https://api.github.com/users/wework/following{/other_user}","gists_url":"https://api.github.com/users/wework/gists{/gist_id}","starred_url":"https://api.github.com/users/wework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wework/subscriptions","organizations_url":"https://api.github.com/users/wework/orgs","repos_url":"https://api.github.com/users/wework/repos","events_url":"https://api.github.com/users/wework/events{/privacy}","received_events_url":"https://api.github.com/users/wework/received_events","type":"Organization","site_admin":false},"repo":{"id":32547526,"name":"wework.github.io","full_name":"wework/wework.github.io","owner":{"login":"wework","id":4072897,"avatar_url":"https://avatars.githubusercontent.com/u/4072897?v=3","gravatar_id":"","url":"https://api.github.com/users/wework","html_url":"https://github.com/wework","followers_url":"https://api.github.com/users/wework/followers","following_url":"https://api.github.com/users/wework/following{/other_user}","gists_url":"https://api.github.com/users/wework/gists{/gist_id}","starred_url":"https://api.github.com/users/wework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wework/subscriptions","organizations_url":"https://api.github.com/users/wework/orgs","repos_url":"https://api.github.com/users/wework/repos","events_url":"https://api.github.com/users/wework/events{/privacy}","received_events_url":"https://api.github.com/users/wework/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/wework/wework.github.io","description":"WeWork Engineering Blog","fork":false,"url":"https://api.github.com/repos/wework/wework.github.io","forks_url":"https://api.github.com/repos/wework/wework.github.io/forks","keys_url":"https://api.github.com/repos/wework/wework.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wework/wework.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wework/wework.github.io/teams","hooks_url":"https://api.github.com/repos/wework/wework.github.io/hooks","issue_events_url":"https://api.github.com/repos/wework/wework.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/wework/wework.github.io/events","assignees_url":"https://api.github.com/repos/wework/wework.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/wework/wework.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/wework/wework.github.io/tags","blobs_url":"https://api.github.com/repos/wework/wework.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wework/wework.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wework/wework.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/wework/wework.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wework/wework.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/wework/wework.github.io/languages","stargazers_url":"https://api.github.com/repos/wework/wework.github.io/stargazers","contributors_url":"https://api.github.com/repos/wework/wework.github.io/contributors","subscribers_url":"https://api.github.com/repos/wework/wework.github.io/subscribers","subscription_url":"https://api.github.com/repos/wework/wework.github.io/subscription","commits_url":"https://api.github.com/repos/wework/wework.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/wework/wework.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/wework/wework.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/wework/wework.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/wework/wework.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/wework/wework.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wework/wework.github.io/merges","archive_url":"https://api.github.com/repos/wework/wework.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wework/wework.github.io/downloads","issues_url":"https://api.github.com/repos/wework/wework.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/wework/wework.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/wework/wework.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/wework/wework.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wework/wework.github.io/labels{/name}","releases_url":"https://api.github.com/repos/wework/wework.github.io/releases{/id}","created_at":"2015-03-19T21:36:55Z","updated_at":"2015-10-06T03:18:09Z","pushed_at":"2015-12-16T16:39:54Z","git_url":"git://github.com/wework/wework.github.io.git","ssh_url":"git@github.com:wework/wework.github.io.git","clone_url":"https://github.com/wework/wework.github.io.git","svn_url":"https://github.com/wework/wework.github.io","homepage":null,"size":4131,"stargazers_count":3,"watchers_count":3,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"open_issues_count":5,"forks":3,"open_issues":5,"watchers":3,"default_branch":"master"}},"base":{"label":"wework:master","ref":"master","sha":"99a19513713c35567fbdfe5c724e9dc6459fa5b7","user":{"login":"wework","id":4072897,"avatar_url":"https://avatars.githubusercontent.com/u/4072897?v=3","gravatar_id":"","url":"https://api.github.com/users/wework","html_url":"https://github.com/wework","followers_url":"https://api.github.com/users/wework/followers","following_url":"https://api.github.com/users/wework/following{/other_user}","gists_url":"https://api.github.com/users/wework/gists{/gist_id}","starred_url":"https://api.github.com/users/wework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wework/subscriptions","organizations_url":"https://api.github.com/users/wework/orgs","repos_url":"https://api.github.com/users/wework/repos","events_url":"https://api.github.com/users/wework/events{/privacy}","received_events_url":"https://api.github.com/users/wework/received_events","type":"Organization","site_admin":false},"repo":{"id":32547526,"name":"wework.github.io","full_name":"wework/wework.github.io","owner":{"login":"wework","id":4072897,"avatar_url":"https://avatars.githubusercontent.com/u/4072897?v=3","gravatar_id":"","url":"https://api.github.com/users/wework","html_url":"https://github.com/wework","followers_url":"https://api.github.com/users/wework/followers","following_url":"https://api.github.com/users/wework/following{/other_user}","gists_url":"https://api.github.com/users/wework/gists{/gist_id}","starred_url":"https://api.github.com/users/wework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wework/subscriptions","organizations_url":"https://api.github.com/users/wework/orgs","repos_url":"https://api.github.com/users/wework/repos","events_url":"https://api.github.com/users/wework/events{/privacy}","received_events_url":"https://api.github.com/users/wework/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/wework/wework.github.io","description":"WeWork Engineering Blog","fork":false,"url":"https://api.github.com/repos/wework/wework.github.io","forks_url":"https://api.github.com/repos/wework/wework.github.io/forks","keys_url":"https://api.github.com/repos/wework/wework.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/wework/wework.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/wework/wework.github.io/teams","hooks_url":"https://api.github.com/repos/wework/wework.github.io/hooks","issue_events_url":"https://api.github.com/repos/wework/wework.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/wework/wework.github.io/events","assignees_url":"https://api.github.com/repos/wework/wework.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/wework/wework.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/wework/wework.github.io/tags","blobs_url":"https://api.github.com/repos/wework/wework.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/wework/wework.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/wework/wework.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/wework/wework.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/wework/wework.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/wework/wework.github.io/languages","stargazers_url":"https://api.github.com/repos/wework/wework.github.io/stargazers","contributors_url":"https://api.github.com/repos/wework/wework.github.io/contributors","subscribers_url":"https://api.github.com/repos/wework/wework.github.io/subscribers","subscription_url":"https://api.github.com/repos/wework/wework.github.io/subscription","commits_url":"https://api.github.com/repos/wework/wework.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/wework/wework.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/wework/wework.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/wework/wework.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/wework/wework.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/wework/wework.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/wework/wework.github.io/merges","archive_url":"https://api.github.com/repos/wework/wework.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/wework/wework.github.io/downloads","issues_url":"https://api.github.com/repos/wework/wework.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/wework/wework.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/wework/wework.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/wework/wework.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/wework/wework.github.io/labels{/name}","releases_url":"https://api.github.com/repos/wework/wework.github.io/releases{/id}","created_at":"2015-03-19T21:36:55Z","updated_at":"2015-10-06T03:18:09Z","pushed_at":"2015-12-16T16:39:54Z","git_url":"git://github.com/wework/wework.github.io.git","ssh_url":"git@github.com:wework/wework.github.io.git","clone_url":"https://github.com/wework/wework.github.io.git","svn_url":"https://github.com/wework/wework.github.io","homepage":null,"size":4131,"stargazers_count":3,"watchers_count":3,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":3,"mirror_url":null,"open_issues_count":5,"forks":3,"open_issues":5,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/wework/wework.github.io/pulls/14"},"html":{"href":"https://github.com/wework/wework.github.io/pull/14"},"issue":{"href":"https://api.github.com/repos/wework/wework.github.io/issues/14"},"comments":{"href":"https://api.github.com/repos/wework/wework.github.io/issues/14/comments"},"review_comments":{"href":"https://api.github.com/repos/wework/wework.github.io/pulls/14/comments"},"review_comment":{"href":"https://api.github.com/repos/wework/wework.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/wework/wework.github.io/pulls/14/commits"},"statuses":{"href":"https://api.github.com/repos/wework/wework.github.io/statuses/7c4cba7fbbbb22771c2ecb63fe038f7c64035997"}}}}