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?
Related
Context:
I'm making an app that allows users to sign in with Google and which then makes calls to the Google Ads API on their behalf.
What works:
Users can sign in and grant the app the necessary permissions, and the API calls go through successfully.
What I'm struggling with:
I'd like to separate the login flow from the permissions flow. In other words, I would like users to be able to log in without granting the app any extraneous permissions, and then, later on, decide if they want to connect to their Google Ads account. This is when Google would prompt them for the necessary permissions.
Relevant code:
I'm using next-auth to handle authentication, setting up the Google provider like this:
./src/pages/api/[...nextauth].ts:
[…]
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
scope: 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/adwords'
}
}
}),
[…]
You can also look at my reproduction repo, a skeletal app that does nothing but make an API call to Google Ads on behalf of the logged-in user. Bootstrapped with create-t3-app using Next.js, tRPC, Tailwind and Prisma.
UPDATE: I can wrap NextAuth(options) and take control of the endpoints
I've discovered that I can use custom initialization of the NextAuth API routes, which should provide me with an opportunity to alter the scope. Unfortunately, I'm still not sure how to provide an askForGoogleAdsPermissions flag to next-auth/react's signIn function in such a way that my customized API handler will be able to tell the difference and add or omit the authorization key in the Google Provider options accordingly.
Update #2: I can provide extra parameters to signIn
Turns out the signIn function can accept additional parameters as a third argument.
This allows me to separate the login from the permissions, but now the API calls I'm making to Google Ads are failing with "PERMISSION_DENIED: Request had insufficient authentication scopes.", despite having successfully granted permissions.
I can confirm the permissions are granted in my Google account, but when I look at the Prisma database managed by next-auth, I don't see the new scope in my account.
Perhaps I can use the signIn callback to make sure the scope is updated? My problem there is that the account I get as a parameter to my signIn callback has the old scope…
To separate the login and grant flows, I had to:
Include only the login scope in the NextAuth GoogleProvider.
Call the signIn method without arguments for a regular sign in
Call if with extra parameters to override the default scope when connecting to Google Ads: signIn('google', undefined, { scope: googleAdsScope })
Save the new scope to the database in the signIn callback
Make sure this isn't overwritten by a token refresh
See my repro repo for the full, working code.
I have an MVC client app (APP1) protected by Identity Server. I use a backchannel mechanism to log out of clients as soon as user logs out of idsrv. Pretty similar to this one: https://github.com/IdentityServer/IdentityServer4/blob/main/samples/Clients/src/MvcHybridBackChannel/Startup.cs
Now the thing is: I need to add a support for external provider, Azure AD. And in turn it also should support automatic log out: when a user logs out of Azure AD, he should be logged out of idsrv and from the client apps.
My first idea was to implement the same approach: Azure App registration supports the ability to call client's endpoint upon log out. But I'm struggling with the part when I need to set up custom CookieAuthenticationEvents. The code which I have in a client app APP1:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies", options =>
{
options.EventsType = typeof(CookieEventHandler);
}
But the same code doesn't work in idsrv. But this I mean that my cookie is not being validated by my custom CookieEventHandler. Can anyone point me in a right direction?
One idea is to let Azure AD call the Logout method on the AccountController to sign out the user. (or if you make your own "logout" endpoint).
The logout method is protected by a ValidateAntiForgeryToken, so you might need to get around that by creating your "own" logout endpoint.
But basically what you need to do on a high level is to let AzureAD trigger a call to the
HttpContext.SignOutAsync(); method.
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 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.
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)