Dynamic configuration inside initializer (devise by example) - ruby-on-rails

I'm developing an Rails 4 app. Auth is against an MS ADFS.
This is my config/initializers/devise.rb (Only omniauth part)
config.omniauth :saml,
issuer: "https://#{ENV['APP_HOST_AND_PORT']}",
idp_sso_target_url: "https://yyy.yyy.yyy/adfs/ls",
assertion_consumer_service_url: "https://#{ENV['APP_HOST_AND_PORT']}/auth/saml/callback",
name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
idp_cert: "xxxxxxxxxx"
This works fine but APP_HOST_AND_PORT need to be setted (of course.)
In this case, APP_HOST_AND_PORT match with HTTP host header + port, DNS domain + port... So I don't really need set this value. The app knows ( or will know ) this value.
But request.host (and request.port) or whatever aren't accessible yet.
There's an elegant way to do this?

Related

omniauth_openid_connect gem - Authentication failure! invalid_request: Rack::OAuth2::Client::Error, invalid_request :: Client credentials are invalid

Im using this gem to add Omniauth OpenID with a provider.
I configured the gem in the Devise Initializer, everything seems to be correct:
config.omniauth :openid_connect,
{
name: :openid_connect,
scope: %i[openid profile groups_rewardops scope_rewardops],
issuer: ConfigSettings.desjardins.issuer_url,
response_type: :code,
uid_field: 'sub',
response_mode: :query,
discovery: true,
send_scope_to_token_endpoint: false,
client_options:
{
port: 443,
scheme: "https",
host: ConfigSettings.desjardins.host,
authorization_endpoint: "/affwebservices/CASSO/oidc/rewardops/authorize",
token_endpoint: "/affwebservices/CASSO/oidc/rewardops/token",
userinfo_endpoint: "/affwebservices/CASSO/oidc/rewardops/userinfo",
identifier: ConfigSettings.desjardins.client_id,
secret: ConfigSettings.desjardins.client_secret,
redirect_uri: "#{ConfigSettings.api.base_url}front_end/users/auth/openid_connect/callback",
},
}
The flow I have atm is that the user can log in and grant access from the provider, then the provider sends a request to my devise callback url with the nonce, code and state. At this point everything seems to be correct but that request ends in failure when trying to generate the access_token with the following error:
ERROR -- omniauth: (openid_connect) Authentication failure! invalid_request: Rack::OAuth2::Client::Error, invalid_request :: Client credentials are invalid.
Im sure the identifier and the secret are correct, don't understand what's going on.
Since Im using discovery mode all the configs of the provider are in the .well-known you can check it here
Im blocked without ideas about how to debug the error. Checking at Rack::OAuth2 to see where the error is comming from I found this that says:
invalid_request: "The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.",
It seems for some reason the access token request is malformed, but not sure what else apart of identifier and secret should I have in mind? I have seen many other examples of configuration and mine seems to be correct.
Since you are sure your credentials are correct, I suspect there is mismatch between the authentication method being used and the methods supported by the provider. Checking the .well-known config, I see this provider only supports client_secret_post. In your omniauth config, I see no options being passed to specify the authentication method. When I dive down into the code, I see that the underlying oauth2 gem defaults to using basic auth, which uses the indentifier and secret to construct an Authentication header. See: source code here
client_auth_method = args.first || options.delete(:client_auth_method).try(:to_sym) || :basic
case client_auth_method
when :basic
cred = Base64.strict_encode64 [
Util.www_form_url_encode(identifier),
Util.www_form_url_encode(secret)
].join(':')
headers.merge!(
'Authorization' => "Basic #{cred}"
)
In the client_secret_post authentication method, instead of providing client secret in the header, the client authorizes itself providing the secret in the HTTP request body as a form parameter. So this provider is not seeing your credentials. You could verify this by looking at the logs of the token endpoint request, which won't be visible in the browser, but rather from your rails BE to the the provider's server.
Try passing a client_auth_method in the client_options hash in your omniauth config. If you look at the case statement in the code I linked to above, there doesn't seem to be a named option for client_secret_post, but it is the default case. Any value for client_auth_method looks like it would work, but I would still use client_secret_post.

Is it possible to set the Micronaut OAuth2 callback-uri as an absolute URL?

I have a Micronaut web-app that uses OpenId / OAuth2 / JWT. In some environments, everything works really well with this set up, however, in other environments, auth fails during the step where the configured callback-uri is called. For some reason, in these environments, the URL generated is "http" instead of "https". This causes the call to fail since my application is only accessible over https.
I have no clue why it is trying to use http in the first place, however, if I was able to specify the callback-uri as an absolute / full URL, then I could probably work around this anomaly in these environments.
An example yml config that I use:
application:
name: xxxxx
security:
authentication: idtoken
oauth2:
enabled: true
clients:
azure:
client-id: ${OAUTH_CLIENT_ID}
client-secret: ${OAUTH_CLIENT_SECRET}
openid:
issuer: https://login.microsoftonline.com/xxx
callback-uri: ${OAUTH_CALLBACK_URI}
redirect:
login-success: ${LOGIN_SUCCESS_URL}
logout: '/logout-handler/logout-success'
endpoints:
logout:
get-allowed: true
token:
jwt:
cookie:
cookie-same-site: none
cookie-secure: true
In this config if I set the callback-uri environment variable (OAUTH_CALLBACK_URI) to /oauth/callback/azure, for example, then the full URL that seems to be used is http://xxxxx/oauth/callback/azure. However, if I use a full URL for the environment variable, e.g. https://xxxxx/oauth/callback/azure then the full URL it uses still appends that as opposed to using it as an absolute URL, i.e. http://xxxxx/https://xxxxx/oauth/callback/azure.
Is it possible to specify this uri as an absolute one and not have it append it like the above effectively duplicating it?
Good news. This was fixed in micronaut-security 2.3.4
https://github.com/micronaut-projects/micronaut-security/pull/644

Problem with using Rails / ADFS integration gem omiauth-wsfed

I have been trying to set my Ruby Rails App to be remotely accessed by a partner of mine which uses ADFS 2.0 for providing SSO possibilities. I have been using omniauth-wsfed gem but failed.
I have set omniauth.rb as below:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :wsfed,
:issuer_name => "http://fs.sib.com.br/adfs/services/trust",
:issuer => "https://fs.sib.com.br/adfs/ls/",
:realm => "https://qa.wit.com",
:reply => "https://qa.wit.com/students/auth/wsfed/callback",
:saml_version => "2.0",
:id_claim => "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
:idp_cert_fingerprint => "--94061be1aba531da005d5f22bf6796b7cd69b3---"
end
Error log is:
ERROR -- omniauth: (wsfed) Authentication failure! invalid_authn_token: OmniAuth::Strategies::WSFed::ValidationError, AuthN token (wresult) missing in callback.
Does anybody suspect what is wrong ?
I am assuming you have configured your omniauth.rb properly where:
Issuer Name: This should be in the format of the adfs sever domain followed by /adfs/services/trust
Issuer: This is where your login requests will be sent, normally it will be the path /adfs/ls on the ADFS server.
Realm: This should match the domain that you provide in your federation metadata document
Reply: This is where you want the response from ADFS to be returned to in your application. This is normally the path /auth/wsfed/callback when using Omniauth.
SAML Version: The version of SAML tokens. Defaults to 2
ID Claim: This is the name of the claim field that ADFS will return that should be used as the unique identifier.
IDP Cert Fingerprint: Your Windows Administrator should be able to tell you this, but if not a way to find it is to put in any string, do a test login to ADFS — this will fail when doing the callback as the certificate doesn’t match, however if you inspect the response in the Chrome Web Inspector you will be able to see the X509 Certificate in the response. You can then use OpenSSL tools, or this online tool to get the fingerprint of the certificate.
Also Setting up callback routes like below
match '/auth/:provider/callback' => 'sessions#create', via: [:get, :post]
match '/auth/failure' => 'sessions#failure', via: [:get]
The **controller#action** can differ depending on how your application is structured.
You can handle the callback in the same way you would any Omniauth provider.
def create
auth = request.env["omniauth.auth"]
auth.uid # Gets the UID value of the user that has just signed in
# Create a session, redirect etc
end
you can refer below repo for further reference.
https://blog.craig.io/using-microsoft-adfs-with-ruby-on-rails-and-omniauth-a26237c64f8d
https://github.com/kbeckman/omniauth-wsfed
Hope it helps.

Running ActionCable behind Cloudfront

We've setup Cloudfront in front of our application, but unfortunately it strips the Upgrade header required for ActionCable to run.
We'd like to have a different subdomain that points to the same servers, but bypasses Cloudfront (socket.site.com, for instance). We've done this and it's somewhat working, but it seems like a persistent connection can't be made. ActionCable continues to retry to make the connection every 10s and seems unable to hold the connection open:
Any advice related to Cloudfront or different domains for ActionCable is appreciated.
To all who follow, hopefully this helps.
As of the time of me writing this (Oct. 2018), it doesn't appear that you can use ActionCable behind Cloudfront at all. CF will discard the upgrade header which will prevent a secure socket connection from ever being made.
Our setup was CF -> Application Load Balancer (ALB) -> EC2. On the AWS side, we began by making a subdomain (socket.example.com) that pointed directly to the same ALB and bypassed CF entirely. Note that Classic Load Balancers absolutely will not work. You can only use ALBs.
This alone did not fix the issue. On your Rails config, you have to add the following lines to your production.rb:
config.action_cable.url = 'wss://socket.example.com:28080/cable'
config.action_cable.allowed_request_origins = ['https://example.com'] # Not the subdomain
You may also need to update your CSP to include wss://socket.example.com/cable for connect_src.
If at this point you're getting a message about failing to upgrade, you need to ensure that your NGINX config is correct. This answer may help.
You will also need to reflect this change in your cable.js. This following snippet works for me with local development as well as production, but you may need to alter it. I wrote it with pre-ES6 in mind because this file never hit Babel in our configuration.
(function() {
this.App || (this.App = {})
var wsUrl
if(location.host.indexOf('localhost') != -1) {
wsUrl = '/cable'
} else {
var host = location.host
var protocol = location.protocol
wsUrl = protocol + '//socket.' + host + '/cable'
}
App.cable = ActionCable.createConsumer(wsUrl)
}).call(this)
That may be all you need, depending on your authentication scheme. However, I was using cookies shared between the main application and ActionCable and this caused a difficult bug. The connection would appear to be made correctly, but it would actually fail and ActionCable would retry every 10s. The final step was to ensure the auth cookies being set would work across the socket subdomain. I updated my cookie as such:
cookies.signed[:cookie_name] = {
value: payload,
domain: ['.socket.example.com', '.example.com']
# Some people have to specify tld_length, but I was fine without it
}

How to configure route for oauth callback

I'm using the gem OAuth2 to communicate with Google services. I don't understand how to implement a callback, which receives the response with the OAuth code to get an access token. When I set a breakpoint in the callback method, it never seems to get called.
Here is my code:
Routes:
match '/oauth2/callback' => 'reports#callback'
Actual redirected url:
http://localhost/oauth2/callback?code=111111
ReportsController:
def new
client = OAuth2::Client.new(ENV['GA_CLIENT_ID'], ENV['GA_SECRET_KEY'], {
:authorize_url => 'https://accounts.google.com/o/oauth2/auth',
:token_url => 'https://accounts.google.com/o/oauth2/token'
})
redirect_to client.auth_code.authorize_url({
:scope => 'https://www.googleapis.com/auth/analytics.readonly',
:redirect_uri => 'http://localhost/oauth2/callback',
:access_type => 'offline'
})
end
def callback
oauth_code = params[:code]
# Create access token with oauth_code
end
Google server is trying to access this url http://localhost/oauth2/callback?code=111111 which is not valid.
You need a domain name to use a service like OAuth, because the google server must be able to find your computer over the internet.
To be able to do this from your development machine you should:
Set a name on a known DNS server: The easiest way to do this is through a Dynamic DNS server like dyndns or no-ip
If you are behind a router using NAT, you might need to redirect the requests to your modem on port 80 to your computer: If you don't do this, your modem will receive a package on port 80 from google and will say "not for me", discarding it. You can do this in your modem menu - look for port forwarding or NAT section on it.
The redirect_url passed to google must match exactly the callback url as seen from client's browser. No problem to use localhost in the url (fotanus sentence about DNS and NAT is wrong). If you are running your container in a different port (e.g. 8080) you have to:
Specify the url in google cloud: http://localhost:8080/oauth2/callback
Specify that same return url in the client request.

Resources