I am building a rails 5 app that is deployed on heroku.
I want to use AWS congnito to achieve single sign on, but there are not enough example to implement it.
I am using devise for authentication. Now my goal is to put my all users on AWS cognito and authenticate them from my rails App.
This is the only resource i found on AWS congnito with rails, I am looking for some example application or a link to tools or ruby API document to achieve this.
Please Help.
Update On basis Of Bala Answer
require 'aws-sdk'
ENV['AWS_ACCESS_KEY_ID'] = 'XXXXXXXXXXXXXXXXX'
ENV['AWS_SECRET_ACCESS_KEY'] = 'XXXX+XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
region_name = 'us-east-1'
endpoint = 'cognito-idp.us-east-1.amazonaws.com'
client = Aws::CognitoIdentityProvider::Client.new(
region: region_name
)
resp = client.admin_create_user({
user_pool_id: "us-east-1_iD7xNHj0x", # required
username: "Test", # required
user_attributes: [
{
name: "email", # required
value: "sachin.singh#example.com",
},
],
validation_data: [
{
name: "Email", # required
value: "AttributeValueType",
},
],
temporary_password: "PasswordType",
force_alias_creation: false,
message_action: "RESEND", # accepts RESEND, SUPPRESS
desired_delivery_mediums: ["EMAIL"], # accepts SMS, EMAIL
})
Error stack trace
home/sachin/.rvm/gems/ruby-2.1.5#global/gems/aws-sdk-core-2.6.38/lib/seahorse/client/plugins/raise_response_errors.rb:15:in `call': User does not exist. (Aws::CognitoIdentityProvider::Errors::UserNotFoundException)
from /home/sachin/.rvm/gems/ruby-2.1.5#global/gems/aws-sdk-core-2.6.38/lib/aws-sdk-core/plugins/idempotency_token.rb:18:in `call'
from /home/sachin/.rvm/gems/ruby-2.1.5#global/gems/aws-sdk-core-2.6.38/lib/aws-sdk-core/plugins/param_converter.rb:20:in `call'
from /home/sachin/.rvm/gems/ruby-2.1.5#global/gems/aws-sdk-core-2.6.38/lib/seahorse/client/plugins/response_target.rb:21:in `call'
from /home/sachin/.rvm/gems/ruby-2.1.5#global/gems/aws-sdk-core-2.6.38/lib/seahorse/client/request.rb:70:in `send_request'
from /home/sachin/.rvm/gems/ruby-2.1.5#global/gems/aws-sdk-core-2.6.38/lib/seahorse/client/base.rb:207:in `block (2 levels) in define_operation_methods'
from aws_cognito.rb:20:in `<main>'
Update 2
resp = client.admin_initiate_auth({
user_pool_id: "us-east-1_uKM", # required
client_id: "3g766413826eul9kre28qne4f", # required
auth_flow: "ADMIN_NO_SRP_AUTH",
auth_parameters: {
"EMAIL" => "kapil.sachdev#metacube.com",
"PASSWORD" => "Ibms#1234"
}
})
First of all, you need to create a user pool for your application
Use this link to create user pool through AWS console
You can find the ruby methods for sign_up, sign_in, change password and many other functions at http://docs.aws.amazon.com/sdkforruby/api/Aws/CognitoIdentityProvider/Client.html
EDIT
Now, you can sign up the user using sign_up
sign_in a user using
admin_initiate_auth
if you need mobile number confirmation, email confirmation you need to configure the user pool that you are creating.
You can find the corresponding methods for confirming the mobile numbers using http://docs.aws.amazon.com/sdkforruby/api/Aws/CognitoIdentityProvider/Client.html#confirm_sign_up-instance_method
Related
I am a newbie Rails 4 developer and need some help adding an API to my simple application using rest-client. For simplification, I will just ask about the API's authentication system.
I have a simple app which uses the Devise gem for authentication. I would like for every user that creates an account to have a calendar for scheduling and booking purposes. to achieve this I am using an API called timekit (http://timekit.io/). Their authentication system responds the to following cURL code example:
curl -X POST \
-H 'Timekit-App: docs' \
-d '{
"email": "doc.brown#timekit.io",
"password": "DeLorean"
}' \
https://api.timekit.io/v2/auth
This will then return the following JSON:
{
"data": {
"activated": true,
"email": "doc.brown#timekit.io",
"first_name": "Dr. Emmett",
"img": "http://www.gravatar.com/avatar/7a613e5348d63476276935025",
"last_name": "Brown",
"last_sync": null,
"name": "Dr. Emmett Brown",
"timezone": "America/Los_Angeles",
"token": "UZpl3v3PTP1PRwqIrU0DSVpbJkNKl5gN",
"token_generated_at": null,
"api_token": "FluxCapacitator", // note that this is usually a randomized string and not an actual word
}
}
So now my questions are the following:
1) Where in the Rails framework do I implement this?
2) How do I do so using rest-client instead of cURL?
3) How do I integrate this with Devise?
4)What are good resources to enhance my own understanding of what I am actually doing here?
Awesome to see your using Timekit (I'm one of the core devs) :)
We don't currently have a ruby gem and I'm not a Ruby developer, but here's a quick code example on how to accomplish this with the HTTParty library:
# Global configs for "admin" user
TK_ADMIN_USER = 'my-email#gmail.com'
TK_ADMIN_TOKEN = '12345ABCD'
# Example usage:
# timekit = Timekit.new(TK_ADMIN_USER, TK_ADMIN_TOKEN)
# tk_user = timekit.create_user(account)
# someInternalUser.update(tk_token: tk_user.token)
class Timekit
include HTTParty
base_uri 'https://api.timekit.io'
headers 'Timekit-App' => 'your-app-name'
def initialize(user, password)
#auth = {username: user, password: password}
end
def create_user(account)
options = {
body: {
"email" => account.email,
"first_name" => account.first_name,
"last_name" => account.last_name,
"timezone" => "America/Los_Angeles"
}.to_json,
basic_auth: #auth,
}
self.class.post('/v2/users/', options)
end
def create_calendar(account)
options = {
body: {
name: "Bookings",
description: "Hold bookings for clients."
}.to_json,
basic_auth: #auth
}
self.class.post('/v2/calendars', options)
end
end
https://gist.github.com/laander/83cb7f5dde1f933173c7
In general, the idea is to create a user through our API (you can do it transparently whenever a user signs up in your onboarding) and then save the API Token that the API returns. After that, you can perform API calls impersonating that users (fetch calendars, create events, query availability etc)
Just write us in the chat on timekit.io if you need more hands-on help!
You might consider creating a small library to wrap interactions with the Timekit web API in the /lib directory of your project. I didn't see a gem for this web API, so you might consider extracting this logic into one so the community can benefit.
The rest-client gem appears pretty easy to use:
auth_hash = {
"email" => "doc.brown#timekit.io",
"password" => "DeLorean"
}
RestClient.post "https://api.timekit.io/v2/auth", auth_hash.to_json, content_type: :json, accept: :json
If you are creating a new Timekit account for each user, you might consider adding the credential to the User model or another related model that stores the credential.
I have a site that is configured to work with multiple Oauth2 API's using Devise with Omniauth and has been functioning normally until last week. Currently login with Twitter and Github still function normally; however, Facebook, LinkedIn and Google are giving me an error stating that the Redirect URI doesn't match. The Error Messages read:
Facebook:
ERROR -- omniauth: (facebook) Authentication failure! invalid_credentials: >OAuth2::Error, :
{"error":{"message":"Error validating verification code. Please make sure your >redirect_uri is identical to the one you used in the OAuth dialog request","type":"OAuthException","code":100,"fbtrace_id":"XXXXXXXXXX"}}
LinkedIn:
ERROR -- omniauth: (linkedin) Authentication failure! invalid_credentials: >OAuth2::Error, invalid_request: missing required parameters, includes an invalid parameter value, parameter more than once. : Unable to retrieve access token : appId or redirect uri does not match authorization code or authorization code expired
{"error_description":"missing required parameters, includes an invalid parameter value, parameter more than once. : Unable to retrieve access token : appId or redirect uri does not match authorization code or authorization code expired","error":"invalid_request"}
Google
ERROR -- omniauth: (google_oauth2) Authentication failure! invalid_credentials: >OAuth2::Error, redirect_uri_mismatch:
{
"error" : "redirect_uri_mismatch"
}
I went reviewed the requests that were sent for all three of these in the Chrome Developers Console and the redirect uri for the callback matches the uri that is registered with each API (Which has not changed since it was working).
The challenge with back tracking this error is I am not 100% sure when these stopped working as I was logging in directly or using the Github login during recent integration tests as I installed new functionality. (Big Lesson Learned!) One of the significant changes that could be impacting this is that I integrated the Traceable extension for Devise which had me require the Warden Gem. However, I removed both the Traceable and Warden configuration and restored the user model and config files to their previous state and I am having the same issue.
I would generally prefer to provide more code samples but to be honest, I am not sure what code to start with. I am hoping that someone has experienced a similar problem and can point in the right direction to start.
To Start, below is my Devise Initializer with Comments Removed to Shorten
Devise.setup do |config|
config.mailer_sender = 'no-reply#' + ENV['DOMAIN_NAME']
config.mailer = 'Devise::Mailer'
require 'devise/orm/active_record'
config.case_insensitive_keys = [:email]
config.strip_whitespace_keys = [:email]
config.skip_session_storage = [:http_auth]
config.stretches = Rails.env.test? ? 1 : 10
config.allow_unconfirmed_access_for = 10.days
config.reconfirmable = true
config.confirmation_keys = [:email]
config.remember_for = 2.weeks
config.expire_all_remember_me_on_sign_out = true
config.password_length = 8..72
config.email_regexp = /\A[^#]+#[^#]+\z/
config.reset_password_keys = [:email]
config.reset_password_within = 6.hours
config.sign_in_after_reset_password = true
config.sign_out_via = :get
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
require "omniauth-google-oauth2" # Added Based on Response to Another Stackoverflow Issues - Did Not Help.
OMNIAUTH = YAML.load(File.read(File.expand_path('../../omniauth.yml', __FILE__))).deep_symbolize_keys
OMNIAUTH.each_value do |provider|
config.omniauth provider[:reference].to_sym, ENV[provider[:key_ref]], ENV[provider[:secret_ref]], { :scope => provider[:scope] }
end
end
The omniauth.yml file that is loaded looks like this:
facebook: { reference: "facebook",
name: "Facebook",
scope: "email, public_profile, user_birthday",
key_ref: "FACEBOOK_KEY",
secret_ref: "FACEBOOK_SECRET" }
twitter: { reference: "twitter",
name: "Twitter",
scope: "r_fullprofile, r_emailaddress",
key_ref: "TWITTER_KEY",
secret_ref: "TWITTER_SECRET" }
linkedin: { reference: "linkedin",
name: "LinkedIn",
scope: "r_basicprofile r_emailaddress",
key_ref: "LINKEDIN_KEY",
secret_ref: "LINKEDIN_SECRET" }
github: { reference: "github",
name: "GitHub",
scope: "user, public_repo",
key_ref: "GITHUB_KEY",
secret_ref: "GITHUB_SECRET" }
google: { reference: "google_oauth2",
name: "Google",
scope: "email, profile",
key_ref: "GOOGLE_KEY",
secret_ref: "GOOGLE_SECRET" }
I had exactly similar issue, facebook working, linkedin and google - not.
After some digging/googling i was able to fix my issue by downgrading:
gem 'omniauth-oauth2', '1.3.1'
So my Gemfile looks like:
gem 'devise'
gem 'koala'
gem 'omniauth-oauth2', '1.3.1'
gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'
gem 'omniauth-linkedin-oauth2'
I went through and updated all of the Omniauth Gems as there was recent version revisions and all of the issues have been resolved.
I'm using the paypal-sdk-merchant gem. So far I have set one merchant account with this command:
PayPal::SDK.configure(
:mode => "sandbox",
:app_id => "APP-sdfgjkl",
:username => "lprieto-facilitator_api1.hasu.cl",
:password => "Z7XGVDCHBJN",
:signature => "AFcWxV21C7fd0v3bYYYRCpSSRlXDCFVGBHLBRTnmAzXxHddoa5e",
:sandbox_email_address => "lprieto-facilitator_api1.hasu.cl")
and then create a payment with
api = PayPal::SDK::Merchant::API.new
This have work perfectly so far but now I have to change the paypal account according to the country the person is buying from. If I'm constantly changing the PayPal::SDK.configure will there be any consistence problems??
For example, if a person in Brasil access and the configuration is change. Then a person in Chile access and the configuration is change. After, the brasilian pays. Will it have the brasilian configuration or the chilean one?
What would you recommend for having multiple Paypal accounts in a ruby on rails app?
Thank you in advance.
I recommend taking a look at https://github.com/paypal/PayPal-Ruby-SDK because this gem doesn't support rails 4 and will be deprecated.
As for your problem at hand: Seeing the API, you would indeed need to call PayPal::SDK.configure() for each different type of merchant/country. You can create a YML config file for this something like config/paypal.yml:
chile:
mode: sandbox
app_id: APP-123
username: user1
password: pass1
signature: ABCDEF
sandbox_email_address: test#example.com
brasil:
mode: sandbox
app_id: APP-456
username: user2
password: pass2
signature: GHIJKL
sandbox_email_address: test2#example.com
and use this in your app like:
#api_chile = PayPal::SDK::Merchant::API.new(:chile)
#api_brasil = PayPal::SDK::Merchant::API.new(:brasil)
Hope this helps!
I am installing the gem spree_chimpy from https://github.com/watg/spree_chimpy in my Ruby on Rails application with spree. After doing mailchimp setup I am notifying mailchimp about merge_vars which seem to cause the error. When I run the command
rake spree_chimpy:merge_vars:sync --trace
I get the error "tag" must be 10 bytes and may only contain "A-Z 0-9 _. I don't understand the error, I have nowhere added any tag variable in the configuration. For reference, my config/initializers/spree_chimpy.rb is pasted here:
Spree::Chimpy.config do |config|
# your API key as provided by MailChimp
config.key = '<#my-api-hash-key>'
# extra api options for the Mailchimp gem
# config.api_options = { throws_exceptions: false, timeout: 3600 }
# name of your list, defaults to "Members"
config.list_name = 'test'
# Allow users to be subscribed by default. Defaults to false
# If you enable this option, it's strongly advised that your enable
# double_opt_in as well. Abusing this may cause Mailchimp to suspend your account.
# config.subscribed_by_default = false
# When double-opt is enabled, the user will receive an email
# asking to confirm their subscription. Defaults to false
config.double_opt_in = false
# id of your store. max 10 letters. defaults to "spree"
# config.store_id = 'acme'
# define a list of merge vars:
# - key: a unique name that mail chimp uses. 10 letters max
# - value: the name of any method on the user class.
# default is {'EMAIL' => :email}
config.merge_vars = {
'EMAIL' => :email,
'FNAME' => :fname,
'LNAME' => :lname,
'ORDERNUMBER' => :onumber
}
end
That error is being thrown is from somewhere around here:
https://github.com/watg/spree_chimpy/blob/7ba5d855112050e1b61c2d0a3369bc08f254842d/lib/spree_chimpy.rb#L59-L62
The actual error is within the mailchimp API client. You're getting the error because ORDERNUMBER has 11 characters in it. If you trim it down to 10 characters, you should be okay.
What I'm trying to have in the end is the ability to login normally with devise OR choose to login with SAML. So I read that if I integrate omniauth and saml, then omniauth and devise, I could achieve that.
My problem is, that I have different IDPs that I would like to choose from. So I don't have one :idp_sso_target_url, but many. So my question is how can I dynamically change the value of the target_url. Currently the omniauth-saml gem defines this value in the config/initializers directory..
Thank you,
You can store settings for every provider in db, and then configure omniauth in the setup phase at request-time. For example:
SETUP_PROC = lambda do |env|
request = Rack::Request.new(env)
user = User.find_by_subdomain(request.subdomain)
env['omniauth.strategy'].options[:consumer_key] = user.consumer_key
env['omniauth.strategy'].options[:consumer_secret] = user.consumer_secret
end
use OmniAuth::Builder.new do
provider :twitter, :setup => SETUP_PROC
end
See https://github.com/intridea/omniauth/wiki/Setup-Phase for more information.
Using multiple SAML IDPs with Devise + OmniAuth:
Follow this official guide for Single IDP.
https://github.com/omniauth/omniauth-saml#devise-integration
Once you have your SP working with single IDP, do following tweaks
In devise initializer
config.omniauth :first, {
name: :first,
strategy_class: ::OmniAuth::Strategies::SAML,
#Rest of the config as per omniauth-saml guide
assertion_consumer_service_url: '/users/auth/first/callback'}
config.omniauth :second, {
name: :second,
strategy_class: ::OmniAuth::Strategies::SAML,
#Rest of the config as per omniauth-saml guide
assertion_consumer_service_url: '/users/auth/second/callback'}
In Users::OmniauthCallbacksController, add actions named first and second in instead of saml as suggested in official guide.
In your User model:
devise :omniauthable, omniauth_providers: [:first, :second]
If all configured correctly, you now have your SP configured for two IDPs.
Rails.application.config.middleware.use OmniAuth::Builder do
provider :saml,
name: "first",
assertion_consumer_service_url: "/auth/first/callback",
issuer: "your-app",
idp_sso_target_url: "first.com/idp"
idp_cert_fingerprint: "E7:91:B2:E1:...",
name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
provider :saml,
name: "second",
assertion_consumer_service_url: "/auth/second/callback",
issuer: "your-app",
idp_sso_target_url: "second.com/idp",
idp_cert_fingerprint: "E7:91:B2:E1:...",
name_identifier_format: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
end
If using the application in a federation context there will be most likely a metadata source, such as prescribed in the saml2int.org profile. This metadata has the data to populate IDP discovery (and automatically configure all the IDPs). It seems that omniauth-saml does not support the SAML Metadata specification, therefore some kind of SAML proxy is the alternative.