Its been an interesting an busy week. I am working on a Rails project and included Grape to implement the API.
The API has 2 sections
No auth required (no headers)
Auth required
I setup the app with and all is working...
Grape
Grape Swagger
Grape Swagger Rails
For stating that a header is required I use some thing like this...
class ProfilesApi < Grape::API
resource :profiles do
desc 'List all profiles' do
headers Authorization: {
description: 'Validates identity through JWT provided in auth/login',
required: true
}
end
get do
present User.all, with: Presenters::ProfilePresenter
end
end
end
Now the problem is that I this description in a lot of similar mountable API classes.
Is there a way that can kind of make this common (kind of inherited) so that I don't need to define it wit every Grape method.
desc 'List all profiles' do
headers Authorization: {
description: 'Validates identity through JWT provided in auth/login',
required: true
}
end
Thanks in advance and hope you guys enjoy the weekend.
Yes, there's a way. I achieve that by defining a method in class API so that it's accessible in everything that inherits from API. Something like:
module Myapp
class API < Grape::API
def self.auth_headers
{ Authorization: { description: 'Validates identity through JWT provided in auth/login',required: true}}
end
end
end
And you access it like that:
desc "List all profiles", {
headers: Myapp::API.auth_headers
}
Of course, there're much more ways but they depend on your implementation.
i think this may be an updated version of grape-api thing, I've had to get this working with :
desc 'My description text' do
headers Authorization: {description: "pass the access token as Bearer", required: true }
end
Related
Before coinmarketcap made their API into tiered free/payed you could get it working with
class Currency < ApplicationRecord
def current_price`
url = 'https://api.coinmarketcap.com/v1/ticker/'
request = HTTParty.get(url + self.slug)
response = JSON.parse(request.body)
end
end
But now it requires an API key which you can get on the basic free tier but I'm at a loss as to where to implement the API key in the above code?
Like I know i need a get and to include the API key but they only mention how to do that with Python C#
The docs clearly state that:
You can supply your API Key in REST API calls in one of two ways:
Preferred method: Via a custom header named X-CMC_PRO_API_KEY
Convenience method: Via a query string parameter named CMC_PRO_API_KEY
With HTTParty providing headers is trivial:
url = 'https://pro-api.coinmarketcap.com/v1/ticker/'
request = HTTParty.get(url + self.slug,
headers: { "X-CMC_PRO_API_KEY" => Rails.application.credentials.coinmarketcap[:pro_api_key] }
)
But you really should avoid doing HTTP calls from your model as it already has way to many responsibilities. Create a separate class instead that touches the application boundary:
class CoinMarketCapClient
include HTTParty
format :json
base_uri "https://pro-api.coinmarketcap.com"
attr_reader :api_key
def intialize(api_key:)
#api_key = api_key
end
def ticker(slug, **opts)
self.class.get("/ticker/#{slug}", headers: {
"X-CMC_PRO_API_KEY" => api_key
})
end
end
I have mounted grape gem over exsting application & in kept grape api related changes in
app >> controller >> api
directory. (which is auto loaded. No code is written for autoloading)
And in that I have code like fetching values form the database table.
module API
module V1
class Users < Grape::API
include API::V1::Defaults
resource :users do
desc 'Creates a User'
params do
requires :role_id,
type: Integer,
values: Role.all.collect { |role| role.id },
desc: 'Role ID'
Here Role.all getting called while setting up new application, which is doesn't exists yet.
But while setting up new application, when I run "rake db:migrate" it gives table doesn't exists error.
How can I stop auto loading of the "api" folder inside controller while setting up new application so that it will not get called.
Or how can I handle above scenario.
& application.rb file where defined grape
module Api
class Application < Rails::Application
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get,
:post, :put, :delete, :options]
end
end
end
end
Thanks in advance.
Above problem was solved by using lazy evaluation of the values.
Used proc for grape values
The :values option can also be supplied with a Proc, evaluated lazily
with each request.
and reframed code will be as below
module API
module V1
class Users < Grape::API
include API::V1::Defaults
resource :users do
desc 'Creates a User'
params do
requires :role_id,
type: Integer,
values: -> { Role.all.collect { |role| role.id } },
desc: 'Role ID'
We can also use class methods inside proc to fasten the query.
What I'm trying to do is to reuse type and description across grape and grape-entity gems.
In the documentation I read the following:
You can use entity documentation directly in the params block with using: Entity.documentation.
module API
class Statuses < Grape::API
version 'v1'
desc 'Create a status'
params do
requires :all, except: [:ip], using: API::Entities::Status.documentation.except(:id)
end
post '/status' do
Status.create! params
end
end
end
This allows me to use field description and field type from the documentation defined in the Grape Entity.
Whenever I define an API that requires only 1 field though, I need to do something like this (which I find kind of dirty):
given:
module Entities
class Host < Grape::Entity
expose :id
# ... exposing some other fields ...
expose :mac_address, documentation: { type: String, desc: "The mac address of the host" }
expose :created_at, documentation: { type: DateTime, desc: "Record creation date" }
expose :updated _at, documentation: { type: DateTime, desc: "Record update date" }
end
end
I can do:
params do
requires :mac_address, type: V1::Entities::Host.documentation[:mac_address][:type], desc: V1::Entities::Host.documentation[:mac_address][:desc]
end
I don't like the above solution mainly for 2 reasons:
I don't like to use the field "type" of an helper that was meant to support documentation generation.
It is cumbersome.
Is there a better way to share type and description across the 2 gems?
You can do like this
module API
class Statuses < Grape::API
version 'v1'
desc 'Create a status' do
params API::Entities::Status.documentation.except(:created_at, :updated _at)
end
post '/status' do
Status.create! params
end
end
end
This will give you only mac_address as a params not all.
background
I have an omniauth-oauth2 subclass strategy working on my rails app. When to refresh access_token, I see I need to create OAuth2::AccessToken. But to create it, it seems it requires OAuth2::Client which I think can obtain from "omniauth-oauth2 subclass strategy."
found this solution Refresh token using Omniauth-oauth2 in Rails application
This is how they solved to obtain a strategy
# the initial param:nil is meant to be a rack object, but since
# we don't use it here, we give it a nil
strategy = OmniAuth::Strategies::YOUR_PROVIDER.new nil, client_id, client_secret
client = strategy.client
your_expired_at_from_your_provider = Time.now.to_i
hash = {
access_token: "your access_token from your provider",
refresh_token: "your refresh_token from your provider",
expires_at: your_expired_at_from_your_provider,
}
access_token_object = OAuth2::AccessToken.from_hash(client, hash)
access_token_object.refresh!
https://github.com/omniauth/omniauth/blob/v1.6.1/lib/omniauth/strategy.rb#L132
https://github.com/intridea/omniauth-oauth2/blob/v1.4.0/lib/omniauth/strategies/oauth2.rb#L35
https://github.com/intridea/oauth2/blob/master/lib/oauth2/access_token.rb#L12
https://github.com/intridea/oauth2/blob/v1.4.0/lib/oauth2/access_token.rb#L82
problem
What I don't understand is, it looks a bit of hacky ways to create a strategy by giving nil to the first argument.
"omniauth-oauth2 subclass strategy" is in rack (like the image below), so I am thinking there is a way to access to a strategy from rack middleware, somewhere?
question
Is creating a strategy like above is the only way to refresh token?
strategy -> client -> access_token_object -> refresh!
I could not find a right way, but make a workaround for my custom omniauth strategy:
class MyOrg < OmniAuth::Strategies::OAuth2
#...
info do
{
'email' => extra['user'].try(:[], 'email'),
# ...
'get_org' => Proc.new do
get_org
end
}
end
def get_org
#org ||= begin
org_id = extra['user'].try(:[], 'org_id')
access_token.get(options[:client_options][:site] + "/v1/orgs/#{org_id}").parsed
end
end
end
Then call it as:
hash[:info][:get_org].call
I leveraged the oauth2 gem to do the refreshing. Here's a complete solution for using the omniauth strategy to access the google APIs: https://stackoverflow.com/a/57191048/2672869
I have currently configured Devise,Doorkeeper and grape in my rails application.
Devise and Doorkeeper are configured so that I can register and login with Devise on the website and Doorkeeper provides oAuth endpoints that can create tokens.
How can I add a token to a HttpRequest and protect the grape API with it?
Edit:
So I tried to implement the Winebouncer implementation Tom Hert suggested.
I followed the instructions on https://github.com/antek-drzewiecki/wine_bouncer
I have installed the gem.
I have defined config/initializers/wine_bouncer.rb as the following.
WineBouncer.configure do |config|
config.auth_strategy = :default
config.define_resource_owner do
User.find(doorkeeper_access_token.resource_owner_id) if doorkeeper_access_token
end
end
I have registered Winebouncer as middleware in grape in my base api controller.
app\controllers\api\base.rb
module API
class Base < Grape::API
mount API::V1::Base
use ::WineBouncer::OAuth2
end
end
I mounted my projects controller in my V1 base controller
app\controllers\api\v1\base.rb
module API
module V1
class Base < Grape::API
mount API::V1::Projects
end
end
end
And this is my projectscontroller
app\controllers\api\v1\projects.rb
module API
module V1
class Projects < Grape::API
version 'v1'
format :json
resource :projects do
desc "Return list of projects" , auth: { scopes: [] }
get do
Project.all
end
end
end
end
end
To be honest I don't yet know how the ", auth: { scopes: [] }" in the description is suppossed to work. And how to add the token to a request, but I would expect my request but be blocked when no token is added. But the the request is still producing the json data.
I found quite interesting code here: https://github.com/fuCtor/grape-doorkeeper
It seems to be still maintained. But I think this is good just to get the idea of what is going on there.
I would recommend this: https://github.com/antek-drzewiecki/wine_bouncer
As said on the page:
Protect your precious Grape API with Doorkeeper. WineBouncer uses
minimal modification, to make the magic happen.
obedeijn, i just noticed your question on stackoverflow.
WineBouncer works just like doorkeeper, it looks for the Authorizations header with a "Bearer x" where x is the token.