Coder Social home page Coder Social logo

rails-github-api's Introduction

Working with APIs

In this lab, we're going to dive into GitHub's OAuth functionality and build our own OAuth Rails application. Refer to this tutorial and the Github OAuth documentation as you work through this lab.

  • http://localhost:3000 is visited. Before a user is routed to something like repositories#index and a view, if the user is not logged in, the application controller redirects the user to GitHub to provide their identity.
  • On successfully confirmation of identity, GitHub redirects the user based on the 'Authorization callback URL' provided when the OAuth application was created. In our case, back to http://localhost:3000/auth. GitHub passes along a code param, as well.
  • http://localhost:3000/auth routes to a sessions controller. In the sessions controller, we use the Client ID and Client Secret, as well as the code param by sending them in a POST back to GitHub. If this information is correct, GitHub will respond with an access_token.
  • By including the access_token on GitHub API requests from our code, GitHub knows we are making authenticated requests and lets us do more things. We can now access account and repository information and even create repositories on behalf of logged-in users!

Objectives

  • Work through configuring OAuth with GitHub on a Rails app

Instructions

  1. First, Set up a new OAuth application on GitHub. For the 'Authorization callback URL' we can use http://localhost:3000/auth. Once set up, you will be provided a Client ID and Client Secret.

  2. Create a .env file where you can store your unique ID and Secret as GITHUB_CLIENT and GITHUB_SECRET. The dotenv gem will read this file, finds out our secrets and securely passes them to our Rails app.

  3. Our Rails application will need to mimic GitHub's OAuth web application flow. The following routes are provided:

get '/auth' => 'sessions#create'
post '/repositories/create' => 'repositories#create'
root 'repositories#index'

A user visiting http://localhost:3000/ will be routed to the root path. Before repositories#index can be invoked, we want to check if the user is authenticated. Write authenticate_user and logged_in? methods in application_controller.rb that will be called before every action. In authenticate_user, if the user isn't logged in (i.g. no session token), we will redirect to GitHub. The base URL here will be https://github.com/login/oauth/authorize and will use the Client ID as a parameter.

  1. If set up properly, running rails s and heading to http://localhost:3000 should cause a GitHub login to appear. Don't worry about authorizing just yet (it is possible to get stuck in a loop here, redirecting to GitHub over and over, since we haven't finished our application). Since we set the 'Authorization callback URL' to http://localhost:3000/auth, once a user signs in to GitHub and authorizes the use of OAuth, they will be redirected back to our app and routed to sessions#create.

  2. In sessions_controller.rb, write a create method. This method should receive GitHub's code parameter and should use it in conjunction with your Client ID and Client Secret to send a POST request to GitHub. The base URL this time will be https://github.com/login/oauth/access_token.

    Note: It is entirely possible to get this workflow working using query params appended to the end of the URL (i.g. https://github.com/login/oauth/access_token?client_id=...). However, it is generally best for security to not send IDs, secrets and tokens this way. Instead, we typically send this content in request headers or in the body.

    For GitHub, we will include our Client ID, Secret and code as part of the body of the request:

    response = Faraday.post "https://github.com/login/oauth/access_token" do |req|
      req.body = { 'client_id': client_id, 'client_secret': client_secret, 'code': code }
      req.headers['Accept'] = 'application/json'
    end

    Notice here, we are also including an 'Accept' header, as well. In this case, we are telling GitHub's server that we will accept JSON as a response.

    If the credentials are correct, GitHub will send a response that includes headers and a body. Within the body is an access token unique to this specific request.

    As is the case whenever data is sent from an API or web server, the response body is sent in the form of a string. Before we can get data from this string, we will need to parse it into a hash:

    body = JSON.parse(response.body)

    The above code parses the response body into a Ruby hash and stores this hash as the body variable. Whatever key value pairs were sent by GitHub will now be available, including body['access_token'].

    We will need this token whenever we send API requests, so the best place to store this would be with in session. Setting something like session[:token] to be equal to the parsed 'access_token' value will allow us to access the token in other controllers.

    After parsing and storing the token as a value in session, redirect to our root path at the end of the create method.

  3. When we are routed to repositories#index, our root path, authenticate_user is called again. This time, however, since there is now a session[:token], the user will not redirected and our repositories/index.html.erb file will be displayed.

  4. Call the GitHub API from within repositories#index to retrieve and display the current user's 'login' in repositories/index.html.erb.

  5. Call the API a second time using https://api.github.com/user/repos to retrieve and display a list of repositories on repositories/index.html.erb. Displaying only the first page of results is fine; feel free to tackle pagination as a bonus.

BONUS: Implement a create action in your RepositoriesController so that the form on index.html.erb successfully creates a new repository for the current user. The form input should be the name of the new repository. Redirect back to '/'. If successful, you should be able to see your newly created repository on your GitHub account.

Hint: You may not be able to trigger pry when testing your application with rails s. However, if you include binding.pry in your controllers, you can trigger it when running tests.

View Working with APIs on Learn.co and start learning to code for free.

rails-github-api's People

Contributors

blake41 avatar changamanda avatar dakotalmartinez avatar danielseehausen avatar dependabot[bot] avatar drakeltheryuujin avatar gj avatar maxwellbenton avatar rrcobb avatar sarogers avatar scottcreynolds avatar sgharms avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rails-github-api's Issues

Hashdiff error

After Bundle Install, I receive the following error message:
The HashDiff constant used by this gem conflicts with another gem of a similar name. As of version 1.0 the HashDiff constant will be completely removed and replaced by Hashdiff. For more information see https://github.com/liufengyun/hashdiff/issues/45.

Expand further to include new Object types

So this is great for an MVP sorta app, but we should show them sorta the full motion.

Right now you are just passing around hashes which is not so good. If you think of any gem that hits an API it has those nice objects that it passes back. Not hashes.

So! I would implement a service object to do the talking to github, and then a new model object that the service object creates. Probably some sort of GithubRepository opbject.

Oauth app vs Github app

The linked tutorial references a Github app. However, to access scopes the app must be a new Oauth app. Following the tutorial could lead to students attempting this lab using a Github app, and that is headache-ville.

Instructions need update / solution code is depracated

After trying with both the code from the solution branch and Dakota's code that previously worked, we were not able to successfully make a POST request to the Github API. All requests keep coming back with a 404 Not Found response. It is possible that the Github API has changed? In any case, this lab needs some love.

quick typo fix

knows ...

By including the access_token on GitHub API requests from our code, GitHub knokws we are making authenticated requests and lets us do more things. We can now access account and repository information and even create repositories on behalf of logged-in users!

running tests does not work

The HashDiff constant used by this gem conflicts with another gem of a similar name. As of version 1.0 the HashDiff constant will be completely removed and replaced by Hashdiff. For more information see liufengyun/hashdiff#45.

An error occurred while loading ./spec/features/repos_spec.rb.
Failure/Error: require File.expand_path("../../config/environment", FILE)

NoMethodError:
undefined method `web_console' for #Rails::Application::Configuration:0x00007fcd1a706c20
Did you mean? console

Requesting headers & passing params in body

  1. In this lab, it was necessary to request an 'Accept' header in the Faraday request. Instructions on how to do this have not been covered thus far in the curriculum, and answers on Google were not basic enough to understand easily. I think it would be VERY helpful to include a section explaining that the tests/Github require a header request to be sent, and show how to do it.

  2. It was also necessary to send a body JSON object in the request to make the tests pass. What was really confusing was that Github does not require you to do it this way (Github will handle it if you send params in the URL), so when I was testing manually, it seemed like it was working. Yet the tests were failing and the failure errors were very difficult to read (I did not realize they were failing only because they wanted me to pass params through a body JSON). I think it would be VERY helpful here to include a note to students along the lines of, "Github will handle params passed in the URL; however, the tests may require you to include the params in the body. If you see a test error that reads "......", the issue is likely that you are passing params in the URL and not as a body JSON. Here is a link to the the previous lesson which explains how to do this: [link]"

CODE THAT REQUIRES BODY JSON and HEADER REQUEST:
resp = Faraday.post url do |req|
req.body = { 'client_id': client_id, 'client_secret': client_secret, 'code': code }
req.headers['Accept'] = 'application/json'
end

session key must be `session[:token]`

I spent hours trying to debug this with the nice people on ask a question. I was setting my session in the controller to session[:gh_token] because I thought it would be good to specify. Resulting error:

visiting root
  lists repos (FAILED - 1)

Failures:

  1) visiting root lists repos
     Failure/Error: visit '/'
     ActionController::RoutingError:
       No route matches [GET] "/login/oauth/authorize"
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.1/lib/action_dispatch/middleware/debug_exceptions.rb:21:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/web-console-2.1.3/lib/web_console/middleware.rb:37:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.1/lib/action_dispatch/middleware/show_exceptions.rb:30:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/railties-4.2.1/lib/rails/rack/logger.rb:38:in `call_app'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/railties-4.2.1/lib/rails/rack/logger.rb:20:in `block in call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.1/lib/active_support/tagged_logging.rb:68:in `block in tagged'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.1/lib/active_support/tagged_logging.rb:26:in `tagged'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.1/lib/active_support/tagged_logging.rb:68:in `tagged'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/railties-4.2.1/lib/rails/rack/logger.rb:20:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.1/lib/action_dispatch/middleware/request_id.rb:21:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/methodoverride.rb:22:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/runtime.rb:18:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/activesupport-4.2.1/lib/active_support/cache/strategy/local_cache_middleware.rb:28:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/lock.rb:17:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.1/lib/action_dispatch/middleware/static.rb:113:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/sendfile.rb:113:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/railties-4.2.1/lib/rails/engine.rb:518:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/railties-4.2.1/lib/rails/application.rb:164:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/urlmap.rb:66:in `block in call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/urlmap.rb:50:in `each'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-1.6.4/lib/rack/urlmap.rb:50:in `call'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-test-0.6.3/lib/rack/mock_session.rb:30:in `request'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-test-0.6.3/lib/rack/test.rb:244:in `process_request'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/rack-test-0.6.3/lib/rack/test.rb:58:in `get'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/rack_test/browser.rb:60:in `process'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/rack_test/browser.rb:38:in `block in process_and_follow_redirects'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/rack_test/browser.rb:37:in `times'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/rack_test/browser.rb:37:in `process_and_follow_redirects'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/rack_test/browser.rb:21:in `visit'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/rack_test/driver.rb:42:in `visit'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/session.rb:227:in `visit'
     # /Users/Roberto/.rvm/gems/ruby-2.2.3/gems/capybara-2.4.4/lib/capybara/dsl.rb:51:in `block (2 levels) in <module:DSL>'
     # ./spec/features/repos_spec.rb:16:in `block (2 levels) in <top (required)>'

Finished in 0.31637 seconds (files took 3.1 seconds to load)
2 examples, 1 failure

This is not an easy bug to catch. It would mean the world to students to students like me to know that the session token must be named [:token], or the test won't work.

Just to clarify, any key name works perfectly in the browser. The only way I found the answer was because repos_spec.rb:23 reads:

describe "new repo form" do
  before :each do
    page.set_rack_session(:token => "1")
  end

Again, a really hard bug to catch...

I'm getting an error that real HTTP requests aren't allowed, so the tests don't pass even though I can see it working in my localhost browser

Failure/Error: visit '/'
WebMock::NetConnectNotAllowedError:
Real HTTP connections are disabled. Unregistered request: GET https://api.github.com/users//repos with headers {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'token 1', 'User-Agent'=>'Faraday v0.9.1'}

   You can stub this request with the following snippet:
   
   stub_request(:get, "https://api.github.com/users//repos").
     with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'token 1', 'User-Agent'=>'Faraday v0.9.1'}).
     to_return(:status => 200, :body => "", :headers => {})
   
   registered request stubs:
   
   stub_request(:post, "https://api.github.com/user/repos").
     with(:body => {"{\"name\":\"a-new-repo\"}"=>true},
          :headers => {'Authorization'=>'token 1'})
   stub_request(:get, "https://api.github.com/user").
     with(:headers => {'Authorization'=>'token 1'})
   stub_request(:post, "https://github.com/login/oauth/access_token").
     with(:body => {"client_id"=>nil, "client_secret"=>nil, "code"=>"20"},
          :headers => {'Accept'=>'application/json'})
   stub_request(:get, "https://api.github.com/user/repos").
     with(:headers => {'Authorization'=>'token 1'})

requires "your_username" to be displayed

In order to pass the tests, the browser needs to see "your_username"

line 6 of repos_spec.rb --> expect(page).to have_content 'your_username'

should accept session[:username] as the way to display the username in the browser and pass the test.

test failure

Failures:

  1. authentication displays the username on the page
    Failure/Error: visit '/auth?code=20'
    WebMock::NetConnectNotAllowedError:
    Real HTTP connections are disabled. Unregistered request: GET https://github.com/login/oauth/access_token?client_id=90401f8e73a3cd4cd146&client_secret=2fc384774296494c75b45f3bb02c2ac5876c422c&code=20 with headers {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.1'}

    You can stub this request with the following snippet:

    stub_request(:get, "https://github.com/login/oauth/access_token?client_id=90401f8e73a3cd4cd146&client_secret=2fc384774296494c75b45f3bb02c2ac5876c422c&code=20").
    with(:headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'User-Agent'=>'Faraday v0.9.1'}).
    to_return(:status => 200, :body => "", :headers => {})

    registered request stubs:

    stub_request(:post, "https://api.github.com/user/repos").
    with(:body => {"{"name":"a-new-repo"}"=>true},
    :headers => {'Authorization'=>'token 1'})
    stub_request(:get, "https://api.github.com/user").
    with(:headers => {'Authorization'=>'token 1'})
    stub_request(:post, "https://github.com/login/oauth/access_token").
    with(:body => {"client_id"=>nil, "client_secret"=>nil, "code"=>"20"},
    :headers => {'Accept'=>'application/json'})
    stub_request(:get, "https://api.github.com/user/repos").
    with(:headers => {'Authorization'=>'token 1'})

./spec/features/repos_spec.rb:5:in `block (2 levels) in <top (required)>'

stub request spec change

despite being able to create new repo's with my application, kept coming up with this error. the difference in the two i found was " with(:body => {"{"name":"a-new-repo"}"=>nil}" , which was tested in line 29 of spec_helper.rb
with(:body => {"{"name":"a-new-repo"}"=>true},
it was only when i changed the spec to nil i was able to pass the test, and still be able to successfully generate a new repo in github. https://github.com/pajamaw/New-Repo-
sorry if i'm incorrect about there being an issue or not, but after comparing this lab with the refactoring lab i think there might be a problem.

 WebMock::NetConnectNotAllowedError:
   Real HTTP connections are disabled. Unregistered request: POST https://api.github.com/user/repos with body '{"name":"a-new-repo"}' with headers {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'token 1', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.2'}

   You can stub this request with the following snippet:

   stub_request(:post, "https://api.github.com/user/repos").
     with(:body => {"{\"name\":\"a-new-repo\"}"=>nil},
          :headers => {'Accept'=>'application/json', 'Accept-Encoding'=>'gzip;q=1.0,deflate;q=0.6,identity;q=0.3', 'Authorization'=>'token 1', 'Content-Type'=>'application/x-www-form-urlencoded', 'User-Agent'=>'Faraday v0.9.2'}).
     to_return(:status => 200, :body => "", :headers => {})

   registered request stubs:

   stub_request(:post, "https://api.github.com/user/repos").
     with(:body => {"{\"name\":\"a-new-repo\"}"=>true},
          :headers => {'Authorization'=>'token 1'})
   stub_request(:get, "https://api.github.com/user").
     with(:headers => {'Authorization'=>'token 1'})
   stub_request(:post, "https://github.com/login/oauth/access_token").
     with(:body => {"client_id"=>"20199bec5e5efcee3e7e", "client_secret"=>"48f0b0637b87e952ab035d62cab89dce67fa0669", "code"=>"20"},
          :headers => {'Accept'=>'application/json'})
   stub_request(:get, "https://api.github.com/user/repos").
     with(:headers => {'Authorization'=>'token 1'})

Cannot run learn or rails s. Nokogiri error

Error with building native extensions. Lab is not working.

Gem files will remain installed in
/Users/parkercatalano/.rvm/gems/ruby-2.3.3/gems/nokogiri-1.6.6.2 for inspection.
Results logged to
/Users/parkercatalano/.rvm/gems/ruby-2.3.3/extensions/x86_64-darwin-17/2.3.0/nokogiri-1.6.6.2/gem_make.out

An error occurred while installing nokogiri (1.6.6.2), and Bundler
cannot continue.
Make sure that gem install nokogiri -v '1.6.6.2' succeeds before bundling.

In Gemfile:
rails was resolved to 4.2.1, which depends on
actionmailer was resolved to 4.2.1, which depends on
actionpack was resolved to 4.2.1, which depends on
actionview was resolved to 4.2.1, which depends on
rails-dom-testing was resolved to 1.0.6, which depends on
nokogiri

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.