How do you add auth to the routes provided by eve_swagger? - eve

I'm using eve_swagger (https://github.com/pyeve/eve-swagger) in a project and it works great for generating swagger docs. I have a use case where I need to have the default /api-docs endpoint that is created as a route in the library require authentication.
When I create my eve app, I set the auth param to my auth class, but /api-docs endpoint is created with a blueprint and I'm not sure how I can add auth to this endpoint. Any ideas?
My eve* deps:
eve = "~0.7"
eve_sqlalchemy = "~0.7"
eve_swagger = "^0.0.11"
Thanks!

I found a way, not sure if this is the best/right way though.
I take the eve_swagger blueprint that's provided, and add a before_request with an authorization function. Something "like" this:
import eve_swagger
from flask import current_app as app
def authorize_swagger:
# my auth logic
eve_swagger.swagger.before_request(authorize_swagger)
app.register_blueprint(eve_swagger.swagger)
The result of doing this is now when I call the default /api-docs route, my authorization function is called and processed before the request. This way if my function decides the request is not authorized, it can stop the request.

Related

How to set custom header for all requests that go through OmniAuth?

This is mostly a thought exercise that I couldn't figure out how to solve. :)
Does anyone know how to set a custom request header for all requests that use the omniauth or omniauth-oauth2 gems?
I've tried something like the following, but I don't see the header in my developer tools in Chrome, for example.
OmniAuth.config.before_request_phase do |env|
env['MY_CUSTOM_HEADER'] = 'true'
end
What am I doing wrong here? What am I missing here?
Edit based on responses:
Why I need the custom header set doesn't really matter here -- as I mentioned at the top of the question: this is just a curiosity I had. The constraint is that I'd like to test the custom header on any OS or browser, so it's not sufficient to just play with developer tools in Chrome here as not all browsers have that capability on across all OSes.
What I'm trying to do is add a custom header to all outgoing OAuth requests coming out the gem. That's it.
In browser developer tools you can only see request headers that are sent by or response headers received by the browser. Any request headers set by load balancers, reverse proxies, middleware and so on - are only visible to next stage in request handling chain.
OmniAuth lives in middleware, so technically any request in your app uses it, unless some other middleware terminates request chain and renders some response.
Also "request" in omniauth terms is not http request, it's an auth phase, usually happens on /auth/:provider (also there're "options" and "callback" phases).
It's not clear why you need setting a request header in a middleware on a request that goes into your own application - when request is going to be handled by omniauth it will not hit your controllers, except for callback phase where request.env['omniauth.auth'] is going to be set.
Since you're mentioned developer tools - probably you want response headers on request phase, to set these you need to override rack response returned from request_phase in your strategy. But for oauth2 there's only a redirect (still possible to set headers, but even less sense).
So first you need to be exact on which headers you want to be set at which request/response, there're several of those. Simplified sequence:
An OAuth request handler cannot force a browser (or any similar user-agent) to disclose more information than specified in the HTTP protocol. Be glad of that: any other posture could lead to information leakage.
The only connection that Omniauth might make itself is that exchanging a code for an access/refresh token. That is specific to the strategy in question, but strategies have the opportunity to include arbitrary headers in their internal client. If you're writing a custom strategy that required a basic authentication header during the access token exchange, it might look like this:
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class DemoStrategy < OmniAuth::Strategies::OAuth2
option :name, "demo"
option :client_options, {
site: 'https://api.example.org',
authorize_url: 'https://auth.example.org/oauth2/authorize',
token_url: 'https://auth.example.org/oauth2/token'
}
uid { raw_info['id'].to_s }
info do
{ email: raw_info['email'], image: raw_info['avatar_url'] }
end
extra do
{ raw_info: raw_info }
end
def raw_info
#raw_info ||= access_token.get('user').parsed
end
def build_access_token
options.token_params.merge!(headers: {
'Authorization' => special_auth_header
})
super
end
def basic_auth_header
"Basic " + Base64.strict_encode64("#{options[:demo_id]}:#{options[:demo_secret]}")
end
end
end
end
Here, build_access_token is overriding the superclass's standard constructor for the internal HTTP client, and injecting extra headers before handing it back up the stack. Internally that's handed off to the oauth2 gem, which in turn uses Faraday, so it's likely anything Faraday accepts is a valid option.
If you need additional information carried to the authentication server, it may be encoded in the redirect URL by the strategy. For example, the omniauth-google-oauth2 strategy is configurable to carry authentication scopes and email hints in the URL that lands on Google's authentication endpoint.
It is also common to include a XSRF state parameter, in conjunction with an encrypted session cookie, to protect against identity spoofing. Depending on the co-operation of the authentication server, some or all of this data may be reflected in the redirection back to your handler.
At it simplest, that is handled by the authorize_params method in the strategy subclass e.g.
def authorize_params
super.tap do |params|
params[:something] = 'my_extra_value'
end
end
However, the volume of code involved in setting up extended parameters may be quite substantial in practice. For a worked example of doing this with Omniauth I'd suggest taking a looking at the source code of the Google strategy, and again I'll draw your attention to the authorize_params method which is the entry point for this heavy lifting.
In the overall flow of things, those are the touch points where server-side code can actually influence matters. There's a fundamental expectation that the user's client/browser is participating by executing nothing but normal HTTPS request.

graphql_devise for authentication in Rails/Graphql/Apollo/React. "field requires authentication" error

I have a project where I am setting up a Rails API with Graphql and React/Apollo. I've purpled all the google links looking for the best authentication solution, but it seems I haven't been able to find a clear answer.
I settled on using the graphql_devise gem, which leverages devise & devise_token_auth. I would have preferred to find a JWT solution, but just couldn't. (If you have any obvious suggestions please tell me!)
First I mounted a separate auth route, but ran into problems with multiple endpoints when I was setting up ApolloClient. I couldn't figure out how to direct auth related requests to my auth endpoint, while letting the rest go through to my graphql one. (If figuring this out is the easiest solution, please tell me!) Instead, I mounted the auth routes in my own schema, as instructed by the docs:
class MyApiSchema < GraphQL::Schema
use GraphqlDevise::SchemaPlugin.new(
query: Types::QueryType,
mutation: Types::MutationType,
resource_loaders: [
GraphqlDevise::ResourceLoader.new('User', only: [:login, :logout])
]
)
mutation(Types::MutationType)
query(Types::QueryType)
And edited the execute line in graphql_controller.rb:
result = MyApiSchema.execute(query, variables: variables, context: graphql_context(:user), operation_name: operation_name)
At this point, running a test query in postman is successful. I can access the graphql route with a userLogin mutation without any headers set, and get a successful response with client, uid & token. Authentication of my other queries also works—success with headers, denied without.
But when I try to perform the same queries using useQuery in react, it doesn't work. In the Apollo Client Developer Tools plugin in Chrome, it doesn't work either, returning only [Object object]. By looking at the request in my Network tab, I can see that this is the result of the same error: "photo field requires authentication".
When I pry into the rails server, I can see that the headers are being received. I can even authenticate the user manually in my graphql_controller before the execute method is called, so I don't think that it is a CORS issue. I have set up the rack-cors gem in Rails to expose the required headers.
When I drill into the code, it seems that the graphql_devise method set_current_resource is failing to return my user. This seems to stem from the devise method set_user_by_token, but I have not been able to figure out why it is failing.
If anyone has any experience with implementing this gem & stack, I would very much appreciate your input! If you have a better way of approaching authentication, I'd love to know what your strategy is. If you can help me solve this ... field requires authentication error, I will love you forever.
Apologies if I've provided too much/too little info, or if my question is too vague. Please let me know if there's something specific I should know to ask/show in my code. Hope you can help! Thanks.
I've managed to find the issue, and I thought I'd explain.
Having traced the problem to the set_user_by_token method in the devise_token_auth gem, I bundle open devise_token_auth'd, and put a byebug inside. This revealed that #token.token was not being extracted correctly from the headers.
The problem was that my header access-token was somehow being converted to accessToken, so when devise tried to set the token info from the request headers using this key, it returned nil.
I do not know why or where this conversion takes place. I suspect it originates from Apollo/React rather than Rails, since the headers on my postman query were not altered. In react, when I set the headers they are set with access-token, as below, but it seems that at some point in the life of my request they are changed.
How I have set the headers in React:
const client = new ApolloClient({
cache,
link: new HttpLink({
uri: 'http://localhost:3000/graphql',
headers: {
accessToken: localStorage.getItem('access-token'),
client: localStorage.getItem('client'),
uid: localStorage.getItem('uid')
},
}),
});
The keys that devise_token_auth uses to select the headers from the request can be changed in the initializers/devise_token_auth.rb file.
I edited them as follows:
config.headers_names = {:'access-token' => 'accessToken',
:'client' => 'client',
:'expiry' => 'expiry',
:'uid' => 'uid',
:'token-type' => 'token-type' }
This means that my front-end is now working smoothly, and I can perform queries/mutations with authentication. However, now in my postman queries I have to change the header to accessToken if I want them to work.
It would be great to know exactly at what point my header from React is changed to camelCase, but for now, it's working. Hope this helps if anyone goes through the same frustrations!

Authenticating docusign via Rails API (Omniauth + Devise) + JS Frontend

I'm trying to create an authentication flow using Auth Code Grant where I've added necessary omniauth strategy for Docusign to create /auth/docusign routes in Rails API only application.
Here are the steps followed
I'm issuing a request to the route from VueJS client.
window.open("http://localhost:4000/auth/docusign", "targetWindow", "width=350,height=250")
After user enters credentials and on successful login I'm calling the callback:
class SessionsController < Devise::SessionsController
def docusign
internal_destroy
#success = false
userinfo = request.env['omniauth.auth']
request_info = request.env['omniauth.params']
if userinfo
info = userinfo.info
cred = userinfo.credentials
user = User.find_by(email: info['email']) || User.find_by(id: session[:user_id])
if user
organization = user.organization
organization.organization_providers.where(provider_name: 'Docusign').destroy_all
OrganizationProvider.create(email: info['email'], token_expires_at: Time.at(cred['expires_at']), token_expires_at: Time.now, provider_name: 'Docusign', organization_id: organization.id, token: cred.token)
#success = true
end
end
render 'sessions/docusign'
end
end
I'd like to pass some params (which I'm accessing in the callback as request.env['omniauth.params']) for executing some backend tasks in the method.
When I try window.open("http://localhost:4000/auth/docusign?email='"+email+"'", "targetWindow", "width=350,height=250")
It says that the url doesn't match with any redirect urls
I have also tried passing in redirect_to('/auth/docusign', query: query) but on doing so, it doesn't open in a browser due to CORS.
I'm also trying to set it in session cookie, but since it's an API only server, I'm still working towards setting up cookie store.
Question
Which is the best way to achieve this? To pass some params in the callback and retrieve it.
Then the execution flow continues on the Rails server and the window serves a page with an appropriate response as per authentication status. However during this time, the client window which started the request is not aware of the authentication outcome.
Question
How can I communicate to the VueJS client that the authentication process is completed?
Question
Am I doing the above flow correctly or are there any better ways to achieve the same?
Thanks in advance
You need to log into your DocuSign Developer Account, Click on Admin and go on the left nav down to "API and Keys" where you can find the integration key you set. Did you set one?
If you did, you should find it and then add the redirectUri to the OAuth settings for that key (client ID in OAuth).
That is why DocuSign login tells you that the redirectURI doesn't match. You can add http://localhost:4000/auth to the list and that should work for your local env.
You cannot past custom variables on the redirectUri, it has to match exactly to the one you entered. If you need to pass values to it, there's a way to do that using state.
Here is how the URL should look, notice the &state= part of it:
https://account-d.docusign.com/oauth/auth?
response_type=code
&scope=YOUR_REQUESTED_SCOPES
&client_id=YOUR_INTEGRATION_KEY
&state=YOUR_CUSTOM_STATE
&redirect_uri=YOUR_REDIRECT_URI
&login_hint=YOUR_LOGIN_HINT
You can put whatever you want in there (URI encoded of course) and that value would come back to you when redirected back also with &state= parameter.
This solves the problem and allows you to pass arguments back to your redirect URI.

Facebook oauth login with custom parameters using Sorcery gem

I'm trying to pass a custom parameter to Facebook oauth url to get it back in the callback. I know it's possible but I'm using Sorcery gem and I need to set the parameter dynamically, I mean I can't define it in sorcery.rb initialization file, like it is now:
Rails.application.config.sorcery.configure do |config|
config.facebook.callback_url = ENV['FACEBOOK_CALLBACK_URL']
...
end
I need to define in my controller, how can I do it with this gem?
I haven't found this documented but it worked, so just put in your controller:
::Sorcery::Controller::Config.facebook.callback_url = ENV['FACEBOOK_CALLBACK_URL'] + "param1=foo"
and param1 will be send back to you after authentication :)
Use the state parameter outlined in oAuth Dialog.
A unique string used to maintain application state between the request
and callback.
Facebook will redirect back to the site with:
YOUR_REDIRECT_URI#
access_token=USER_ACCESS_TOKEN
&expires_in=NUMBER_OF_SECONDS_UNTIL_TOKEN_EXPIRES
&state=YOUR_STATE_VALUE

How to generate an API Key and Secret in Ruby on Rails?

E.g. API_key: 4faa86aa5848207502000002 and API_secret 7375d7d1e89d3d602b184432fbcf3c09c7cb30676f19af9ac57d228be401.
Should I use SecureRandom?
Thanks!
ActiveSupport::SecureRandom would work for the actual generation, but you should also consider a way to invalidate and reset the token on different events.
Since you're using Devise, take a look at the Token Auth Strategy. You could write a similar strategy with two tokens (API Key and API Secret, respectively). You need to write both the strategy and the Model, but in both cases the Token Auth example gets you pretty far.
As a starting point (from the Token Auth example), your model should declare both required parameters.
module Devise
module Models
module APIKeyAuthenticatable
...
def self.required_fields(klass)
[:api_key, :api_secret]
end
def reset_keys
self.api_key = self.class.api_key
self.api_secret = self.class.api_secret
end
You might also want to read Custom authentication strategy for devise. If you're looking to provide a more full-featured API auth solution atop devise devise_oauth2_providable looks pretty good.
I have tried authlogic_api. It was fairly easy to implement.

Resources