I am trying to implement AWS Cognito into my application for better all round authentication. The system is a Rails application that is currently using Warden/Devise as the method for handling user accounts (Login,Registration).
My goal is to have a AWS UserPool that contains the list of users for the application. When a user is verified using Cognito I wish to then search the tables that we currently use for the role and move the user to the correct area of the system based on the role that they are assigned too.
I have started to implement the logic to handle this but have come up against a brick wall.
Please see below my code.
cognito_authenticatable.rb
Logic for handling the cognito authentication. All i want to do here is check that the user is registered and return the valid token so i can prefer internal application checks to gather the user role.
def authenticate!
if params[:login]
region_name = 'us-east-2'
user_pool_id = 'us-east-2_Qj78BNQon'
client_id = '1pv3eno72e51mll3q36cuiojmr'
client = Aws::CognitoIdentityProvider::Client.new(
region: region_name
)
resp = client.initiate_auth({
client_id: client_id,
auth_flow: "USER_PASSWORD_AUTH",
auth_parameters: {
"USERNAME" => email,
"PASSWORD" => password
}
})
end
end
divise.rb
This code is just to add the new authentication strategy to the applications warden service.
config.warden do |manager|
manager.strategies.add(:cognito,
Devise::Strategies::CognitoAuthenticatable)
manager.default_strategies(:scope => :login).unshift :cognito
manager.default_strategies(:scope => :login).pop
end
The output error within the console is
Aws::Errors::MissingCredentialsError (unable to sign request without credentials set):
config/initializers/cognito_authenticatable.rb:23:in `authenticate!'
and here is an image from the localhost application that was running.
Any help on this would be amazing.
Thanks in advance.
One solution could be to uncheck the option for generating a client secret when you create the app client in the Cognito user pool. This option is checked by default and you have to know to uncheck it (https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html).
By default, user pools generate a client secret for your app. If you don't want that to happen, clear Generate client secret.
It's only possible to uncheck the client secret during the creation of a new client, so you might have to delete your client app and create a new one (not a big deal).
I also collect my learnings on Cognito, Devise, Rails and VueJS in a Medium article: https://medium.com/#morgler/beta-learnings-from-developing-vuejs-quasar-aws-amplify-and-cognito-application-dd38ec58b881
You are getting this error due to your AWS SDK for Ruby not being configured correctly. That error would likely exist not only for Cognito APIs, but it would exist for any AWS Signature V4 signed API calls. Kindly refer to this documentation to configure your SDK correctly for your application.
Related
I have a Rails 6 application and I want to use Azure Active Directory as an authentication system (with open id connect, saml2 and ldap).
The authentication is done.
Now I am trying to display user information like names or email addresses. I also want to be able to export all users of a directory.
I have tried to set a configuration up like so:
In my Rails app, in the admin panel, an admin can configure Azure AD for my application
in the config, the admin copies and pastes the configuration link provided by Azure AD (a JSON response)
Then, copies and pastes the app client_id
Then, the tenant_id (directory id)
Here is a piece of code that I expected to work:
def update_oidc
identity_provider = IdentityProvider.find_by(provider_type: 'open_id_connect', id: params[:id])
client_id = params[:client_id].strip
metadata_url = params[:metadata_url].strip
tenant_id = params[:tenant_id].strip
metadata = HTTParty.get(metadata_url).parsed_response
identity_provider.update(config: {
metadata: metadata,
metadata_url: metadata_url,
client_id: client_id,
tenant_id: tenant_id,
})
if tenant_id
directory_access_url = "https://graph.windows.net/#{tenant_id}/users?api-version=1.6"
result = HTTParty.get(directory_access_url).parsed_response
identity_provider.directories.find_or_create_by(tenant_id: tenant_id).update(
business_phones: result["business_phones"],
display_name: result["display_name"],
given_name: result["given_name"],
job_title: result["job_title"],
email: result["user_principal_name"],
mobile_phone: result["mobile_phone"],
office_location: result["office_location"],
surname: result["surname"]
)
end
redirect_to identity_provider
end
As the tenant_id is the directory id, i thought that we might be able to access user info this way (and following the Microsoft Docs). The thing is, it doesn't work because even though I'm connected to my Azure AD directory in my app, when I run result = HTTParty.get(directory_access_url).parsed_response, i have an authentication error telling me the token has expired or that i need to be connected.
I don't want to use PowerShell or anything like this. I want to be able to access directories data through my app.
Can someone tell me what i'm doing wrong or come up with an idea ?
Thanks
Just according to your code, I think you want to get the collection of users via the Azure AD Graph REST API Get users using jnunemaker/httparty library.
However, it seems to be missing the required header Authorization with its value like Bearer eyJ0eX ... FWSXfwtQ as the section Authentication and authorization of the offical document Operations overview | Graph API concepts said. Meanwhile, you have done the authentication with OpenID Connect, but Azure AD Graph API requires the access token as Authorization value from OAuth2 as the content below said.
The Graph API performs authorization based on OAuth 2.0 permission scopes present in the token. For more information about the permission scopes that the Graph API exposes, see Graph API Permission Scopes.
In order for your app to authenticate with Azure AD and call the Graph API, you must add it to your tenant and configure it to require permissions (OAuth 2.0 permission scopes) for Windows Azure Active Directory. For information about adding and configuring an app, see Integrating Applications with Azure Active Directory.
Azure AD uses the OAuth 2.0 authentication protocol. You can learn more about OAuth 2.0 in Azure AD, including supported flows and access tokens in OAuth 2.0 in Azure AD.
So I'm afraid you have to get the access token manually via OAuth2 for Azure AD again for using Graph API, or just simply refer to the sample code samples/authorization_code_example/web_app.rb using the adal library of GitHub repo AzureAD/azure-activedirectory-library-for-ruby for Ruby.
I have implemented a AWS Lambda function and used the gateway to return the fulling data:
var param =
{
IdentityPoolId: "actualIdentityPoolId",
Logins: {} // To have provider name in a variable
};
param.Logins["com.testing.userLogin"] = userId;
cognitoidentity.getOpenIdTokenForDeveloperIdentity(param,
function(err, data)
{
if (err) return fn(err); // an error occurred
else fn(null, data.IdentityId, data.Token); // successful response
});
So the identityId and token get sent back to the ios device. In my device I try to connect to an AWS DynamoDB table but access is denied. How do I use the identityId and token to gain access to the tables?
I have set up roles in IAM for Unauth which denies Dydnamo and Auth which gives access to the tables through its policies.
I am trying to implement authentication using: http://docs.aws.amazon.com/cognito/latest/developerguide/authentication-flow.html
I see there are two flows which are Basic and Enhanced. The documentation says most users will use the enhanced flow and that implements GetCredentialForIdentity.
How is that implemented in my ios code so that I can switch my role from unauth to auth and can access to dynamodb? How long will this access last? I would like to do this all in my ios code instead of using lambda or something else like that.
If your user is unauthenticated, then logs in you need to clear your credentials, and your 'logins' method should now return a properly updated logins map.
Here is the documentation to help you:
http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html
Double check your DynanoDB Roles for authenticated access your DynamoDB resource. An example role for this are on the following page of the developer guide you referenced. The page is called "IAM Roles" and the last section is the important one: "Fine-Grained Access to Amazon DynamoDB".
Stick with your plan to use the Enhanced Authflow. It is recommended and makes less calls to authenticate (your users will appreciate this). Just make sure you mobile clients call GetCredentialsForIdentity from iOS.
From the Enhanced Authflow documentation further down your page:
The GetCredentialsForIdentity API can be called after you establish an identity ID. This API is functionally equivalent to calling GetOpenIdToken followed by AssumeRoleWithWebIdentity.
The AssumeRoleWithWebIdentity is the important piece that allows your user to assume the Role that gets access to the DynamoDB resource. Cognito will take care of the rest as long as you set up the Roles correctly within the Cognito console:
In order for Amazon Cognito to call AssumeRoleWithWebIdentity on your behalf, your identity pool must have IAM roles associated with it. You can do this via the Amazon Cognito Console or manually via the SetIdentityPoolRoles operation (see the API reference)
I am attempting to create an iOS app in Swift that uses the following authentication service using AWS Lambda - https://github.com/danilop/LambdAuth
It uses the AWS Mobile SDK for iOS to communicate with DynamoDB and Lambda - http://docs.aws.amazon.com/mobile/sdkforios/developerguide/
Here is the sample code for the website that utilizes the token returned from the Lambda login function, I imagine the Swift code will be something similar - https://github.com/danilop/LambdAuth/blob/master/www/login.html#L69
Here is the cloud function that generates the token for the user - https://github.com/danilop/LambdAuth/blob/master/LambdAuthLogin/index.js#L102
I have created an identity pool in AWS Cognito (Federated Identities) and I have two roles, auth and unauth. My application appears to always being the unauth role (arn:aws:sts::123123123:assumed-role/_unauth_MOBILEHUB_123123123/CognitoIdentityCredentials). My users are being stored in a dynamodb table, with a salted password.
The root of the problem is that I don't know the correct Swift code to write after I receive a login token from the service to transition my user into the authenticated role (use the auth arn). I want it to be using the auth role for every service call to AWS (dynamodb, lambda, etc). I'm hoping that someone can point me in the right direction - thank you.
As per the design in Danilo's book, if you are using the aws-sdk javascript , you should define your objects like :
var creds = new AWS.CognitoIdentityCredentials({
IdentityPoolId: //hard coded value for your system//
})
AWS.config.update({
region: 'us-east-1',
credentials: creds
});
var lambda = new AWS.Lambda();
then once you receive your identityId and token , you should assign them to you creds as follow :
creds.params['IdentityId'] = output.identityId;
creds.params['Logins'] = {};
creds.params['Logins']['cognito-identity.amazonaws.com'] = output.token;
creds.expired = true;
where output is the response from your LambdAuthLogin Lambda function.
Authenticated roles will only be used when you use one of the supported public providers (Facebook, Google, Twitter, Login With Amazon), OIDC provider, SAML provider or Cognito User Pool users.
Amazon Cognito User Pools provides you the solution for user registration, authentication and management. Is there is reason that you prefer using Lambda Auth over that?
Now I'm looking for more than two days for a solution for the following problem. I have an EmberJS client-side javascript app, that uses my server-side Rail RESTful API. Authorization is done via access token.
Now I want to give the user the ability also to login with OAuth2 from Google, Twitter and so on. Discourse uses OmniAuth for third party login, but OmniAuth needs a server side session. Because I build a RESTful and stateless API, I didn't want to use a session on the server. So I decide to build it on my own with help of Google+ Sign-In for server-side apps, but the example there also uses a session.
Does anyone have a solution for a similar problem or some hints for solving my problem?
EDIT 1
Because OmniAuth doesn't fit well in my setup, I started to create a own implementation for third-party OAuth2 login following Googles help. Everything works fine at the moment. But I didn't implement the CSRF protection explained under heading 1. Create an anti-forgery state token on the site from Google mentioned above. My problem is, how could I store this CSRF token without using a session. Would it be enough to store it in Database and look it up in the callback request from Google later?
EDIT 2
I followed this railscast. There three possible cases, if a user want to sign-in with an extern oauth provider:
The user already signed-up with extern oauth, then he got a Doorkeeper access token.
The user has an account, but didn't sign-in with extern provider before. After oauth flow we only have to create a new authentication for this user.
The user didn't have an account and now tries to sign-in with extern provider. Here we have to redirect the user to the sign-up page, we also can use informations from the oauth provider to pre-fill in the sign-up form, but until the user pushes the sign-up button, we have to save the authentication.
My question now is, what is a good practice to save such informations (authentication, oauth2 csrf-token) server-side in a REST API, without using a session. I have to save these information on the server, because the user should not have the possibility to manipulate them on the client-side.
Maybe I also should create a new question for pros and cons of token and session authentication with Ember apps and possible solutions for both?
Here is my authentication controller:
class Api::V1::AuthenticationsController < ApplicationController
def oauth
# redirect to google/twitter/...
login_at(params[:provider])
end
def callback
# callback from provider
provider = params[:provider]
if #user = login_from(provider)
doorkeepter_token = Doorkeeper::AccessToken.create!(:resource_owner_id => #user.id)
#data = {
access_token: doorkeepter_token.token,
user: #user
}
render 'oauth/complete'
else
# user has no account, create a new one
#user = User.new
#user.email = #user_hash[:user_info]['email']
#user.authentications.build(:uid => #user_hash[:uid], :provider => params[:provider])
#user.oauth_pending!
if #user.save
doorkeepter_token = Doorkeeper::AccessToken.create!(:resource_owner_id => #user.id)
#data = {
access_token: doorkeepter_token.token,
user: #user,
errors: #user.errors
}
render 'oauth/complete'
else
render 'oauth/error'
end
end
end
end
There's an example in the Ember.SimpleAuth repo that shows how to use Facebook as an external authentication provider: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-external-oauth/index.html. It basically does it the same way Discourse does it or was doing it 2-3 months ago (not sure whether they changed it) while it doesn't require a server side session.
Supporting Google would work basically the same way.
I updated the Facebook Auth example so that the server part is optional - it now uses the Facebook JS SDK: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-facebook-auth.html. Maybe that help you to get an understanding how Google Auth could be implemented - I'm sure it's going to work quite similarly.
Have you seen the ember-simple-auth project? It supports OAuth2.
I'm planning to use rails-api for providing JSON API for iOS mobile application to consume. The process:
User open the mobile app
User taps on Facebook connect
Mobile app get fb_access_token and post it to API server to identify the user
API server get user profile on Facebook by using fb_access_token
API server either create and look up the user, then response with a api_token for this particular user
Mobile app use the api_token response for all communication afterward.
Which authentication should be the best option for this app? oAuth2 or BasicAuth? I tried rails-api with doorkeeper, but it doesn't work out of the box because doorkeeper need some assets.
I am doing a basic authentication for this integrated with devise.
First i get the post parameters from the mobile application (the access_token and other stuff).
Then I use open-api to get the user details from facebook:
url = "https://graph.facebook.com/me?access_token="
begin
content = open(URI.encode(url + params[:user][:access_token]))
rescue OpenURI::HTTPError #with this I handle if the access token is not ok
return render :json => {:error => "not_good_access_token" }
end
Now Facebook returns the response
status = content.status[0]
content = ActiveSupport::JSON.decode(content)
if status == "200"
#get the email and check if the user is already in the database. If there is not email, check by the facebook id
#If the user exists return the user. If the user does not exists create new
Hope this helps
Than you can user the same code also for google, just change the url to "https://www.googleapis.com/oauth2/v2/userinfo?access_token="
I'd give omniauth-facebook at try, as it uses OAuth2 and is pretty easy to use. All omniauth strategies are rails middleware, so you just need to add gem 'omniauth-facebook' to your gemfile and add the following to config/initializers/omniauth.rb and you will be able to use /auth/facebook to log in via facebook and /auth/facebook/callback to create the user session (you might want to alter the :display key-value as pop-ups in mobile might not be aesthetically pleasing):
Rails.application.config.middleware.use OmniAuth::Builder do
provider :facebook, ENV['FACEBOOK_KEY'], ENV['FACEBOOK_SECRET'],
:scope => 'email,user_birthday,read_stream', :display => 'popup'
end
The facebook auth token will be in the request.env['omniauth.auth'][:credentials][:token] that gets returned to your callback endpoint, which you can integrate into your mobile authentication strategy. see the above link and the omniauth main page for more details.