AWS Cognito: Developer Authenticated Identities - ruby

These are the calls I'm making to get the access token from AWS Cognito. I am implementing the Developer Authenticated Identities workflow
where I authenticate the user on my backend. My code:
cognitoIndentityClient = Aws::CognitoIdentity::Client.new(
region: 'us-east-1',
credentials: permanent_aws_creds,
)
developerProviderName = '1.Got From Developer Provider Name under Custom in Cognito Console'
identityPoolId = 'us-east-1:Xxxxx'
resp = cognitoIndentityClient.get_open_id_token_for_developer_identity(
identity_pool_id: identityPoolId,
logins: {
developerProviderName => UniqueIdentityTokenProviderFromMYBackend
}
)
resp2 = cognitoIndentityClient.get_credentials_for_identity(
{
identity_id: resp['identity_id'],
logins: {
'cognito-identity.amazonaws.com' => resp['token']
}
}
)
My Question:
1. How can I create a user in the user pool (enable MFA and all that) after the above calls? I can see that Identities are created in my console but I'm lost after that.

Can you check that in identity pool configuration in custom tab of Authentication providers section you have the developer provider name set and it matches with the value in your code above? This might be one possible reason for the error.
To answer your other question. You do not need to implement developer authenticated identities to use the 'User Pools' feature of Cognito. These are two independent features. Cognito developer authenticated identities allows you to federate your own authentication system with Cognito identity. If you want Cognito to manage your users and allow username and password based sign-up, sign-ins and MFA for you, 'User Pools' feature will be correct choice. The user managed by User pools can also federate with Cognito identity.

Related

Removing a user from a group in a Cognito user pool doesn't take effect until they logout and back in

We have a Cognito User Pool which is connected to an AppSync API. In the graphql.schema we limit which users can access which endpoints like this:
type Mutation {
createProject(projectInput: CreateProjectInput!): Project!
#aws_auth(cognito_groups: ["StandardUsers"])
}
The AppSync endpoints fire Lambdas which get the details of the Cognito user used to authenticate like this:
const cognitoIdentity: AppSyncIdentityCognito = event.identity as AppSyncIdentityCognito
const user: User = {
id: cognitoIdentity.sub,
username: cognitoIdentity.username,
groups: cognitoIdentity.groups
}
We're authenticating using the Amplify JS library.
The user can happily hit the endpoint when part of the Cognito group, but if I remove them (via the AWS console) they can continue to hit the endpoint!!
If they logout and back in they are then denied access to the endpoint.
How can have an immediate "kill switch" to ensure users no longer have these privileges?
Cognito is a stateless authentication method by design. Once a user logs in they are given tokens for that particular session. So until these tokens expire they get all the privileges defined in these tokens. You cannot manually expire these tokens once issued, but you can blacklist them using your own implementation.
Your requirement is for stateful authentication. You will need to implement this yourself using a database and custom authorizers that check for "blacklisted" tokens.

Google Admin API with service account -- bad credentials

I'm trying to write console app code to update Google Directory with values pulled from a SQL database. I can't seem to make the API connect successfully when using a service account. Any ideas? I've whittled the code down to just the essentials here.
static void Main(string[] args)
{
try
{
// Create a ServiceAccountCredential credential
var xCred = new ServiceAccountCredential(new ServiceAccountCredential.Initializer("saxxxxxxxx#directorysync-xxxxxx.iam.gserviceaccount.com")
{
Scopes = new[] {
DirectoryService.Scope.AdminDirectoryUser,
DirectoryService.Scope.AdminDirectoryUserReadonly
}
}.FromPrivateKey("-----BEGIN PRIVATE KEY-----\nMI...p9XnI4DZFO/QQJc=\n-----END PRIVATE KEY-----\n"));
// Create the service
DirectoryService service = new DirectoryService(
new BaseClientService.Initializer()
{
HttpClientInitializer = xCred,
}
);
var listReq = service.Users.List();
listReq.Domain = "mycompany.com";
listReq.MaxResults = 100;
listReq.OrderBy = UsersResource.ListRequest.OrderByEnum.Email;
Users results = listReq.Execute();
// process the users list here...
}
catch (Exception e)
{ Console.WriteLine(e.Message); }
}
The error happens at the .Execute() line:
Google.Apis.Requests.RequestError
Not Authorized to access this resource/api [403]
Errors [
Message[Not Authorized to access this resource/api] Location[ - ] Reason[forbidden] Domain[global]
]
I've tried code seen elsewhere (How to login to Google API with Service Account in C# - Invalid Credentials) to bring in the whole contents of the .JSON file that contains the credentials for the service account; that made no difference. I'm not the google domain admin, but the admin built the credential and promises that it does, indeed, have rights to the user resources. I'm utterly lost at what's not right.
Either:
You're missing the email address of an admin user to impersonate.
An Admin of the domain needs to assign user management privileges to the service account.
Background
There's two HTTP requests involved in making a Google API request with a service account:
Using the service account's credentials to request an access token from the Google OAuth 2.0 server. A HTTP POST is sent with a JWT signed with the private key of the service account. If successful, an access token is returned which is valid for one hour.
Making the service API request (Admin SDK Directory API in this case) using the OAuth access token obtained from the last step.
The error message you provided is not listed in the JWT error codes page so step 1 is working, and the error is coming from step 2 - the request to Directory API.
You should be able to confirm this using an HTTPS request interceptor like mitmproxy.
You'd get a 403 error for the Directory API users.list method for a few reasons:
You've authenticated as a service account, but that service account has no admin privileges which are sufficient for the request.
You've authenticated as a user in the domain (either using a service account with impersonation with the sub parameter in the JWT request, or using three-legged interactive OAuth), but that user has no admin privileges which are sufficient for the request.
You've authenticated as a service account or user with sufficient privileges, but the domain parameter is not owned by the customer for which the service account or user is associated with.
In your sample code, there is no email address of a domain user (admin) specified (the email address for ServiceAccountCredential.Initializer is actually the OAuth client name of the service account, not a real email address), so it is either case 1 or 3.
To address case 1
A service account has no association with a domain or access privileges by default.
An admin can open Admin console, go to "Account > Admin roles > "User Management Admin" > Admins > Assign service accounts", then:
Add the service account name (looks like an email address, ending #gserviceaccount.com).
Click "Assign role".
Now, the service account does not need to impersonate another admin in the domain, it will directly have admin privileges.
A couple of side-effects of this:
The service account cannot be "suspended" as such, only removed from the admin role.
Audit logging of the actions will show the service account name in the "Reporting > Audit > Admin" section of Admin console.
To address case 2
You may have meant to impersonate an admin user in the domain, which you could do by adding:
, User = "admin#example.com"
- after the Scopes array passed to ServiceAccountCredential.Initializer.
This adds the email address of a user to the JWT request to retrieve an access token for that user (by adding the sub field to the claim-set of the JWT assertion).
To address case 3
Replace the "mycompany.com" on the line:
listReq.Domain = "mycompany.com";
- with a domain that is associated with the customer, or instead, remove that line and add:
listReq.Customer = "my_customer";
(literally my_customer - see users.list query-parameters)
- Which will list users on all domains associated with the customer (Google Workspace and Cloud Identity customers can have many secondary domains).

How to debug Authentication configuration?

We are experiencing problems with Authentication of Service Accounts for domain-wide delegation.
The main problem is it's hard to investigate and debug the auth configuration so
we would like to ask for some tips how to debug the configuration.
Or maybe we are missing some configuration options and you can point us to them.
Our process is:
Create SA(=Service Account) with enabled domain-wide delegation.
Authenticate SA in GSuite admin console(https://support.google.com/a/answer/162106?hl=en).
use client_id from the credentials file. (now email)
scopes are comma-separated without spaces between.
Ensure the "Security > API Reference > API Reference -> 'Enable API Access'" is checked.
For some GSuite domains this is working configuration, but we got some domains where this configuration results in:
google.auth.exceptions.RefreshError: ('unauthorized_client: Client is unauthorized to retrieve access tokens using this method.', '{\n "error": "unauthorized_client",\n "error_description": "Client is unauthorized to retrieve access tokens using this method."\n}')
In our understanding, this is the error saying the client_id and scopes were not added to the "Manage API client access" page. (=List of authenticated clients)
We really ensured that the GSuite domain we are requesting has the proper client_id and scopes added in the list of authenticated clients + has the 'Enabled API Access'.
We even created Shared Desktop with them and did it by ourselves to be fully sure of it.
But the error still persists.
However, we are not able to replicate this problem on our test GSuite domain.
We tried couple of options using same SA as the client:
The impersonated account hasn't permissions to access the resource.
This result in:
googleapiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&alt=json returned "Not Authorized to access this resource/api">
The scopes are just partial:
google.auth.exceptions.RefreshError: ('access_denied: Requested client not authorized.', '{\n "error": "access_denied",\n "error_description": "Requested client not authorized."\n}')
The 'Enabled API Access' is not checked.
googleapiclient.errors.HttpError: https://www.googleapis.com/admin/directory/v1/users?customer=my_customer&alt=json returned "Domain cannot use apis.">
The error we are receiving from the client("Client is unauthorized to retrieve access tokens using this method."), we are able to replicate only if the client_id is not in the list of authenticated clients at all.
But we are sure, the problematic GSuite domains have the SA authenticated in "Manage API client access" page.
We are using these scopes: https://www.googleapis.com/auth/userinfo.profile,https://www.googleapis.com/auth/userinfo.email,https://www.googleapis.com/auth/gmail.readonly,https://www.googleapis.com/auth/plus.login,https://www.googleapis.com/auth/calendar.readonly,https://www.googleapis.com/auth/contacts.readonly, https://www.googleapis.com/auth/admin.directory.user.readonly
Do you have any ideas how to debug/solve this issue?
Here is what you need to do. Double check each step. If in doubt, start over.
Enable "Admin SDK API. This is enabled on a per project basis.
Create a service account. Do not add or remove any privileges. Don't change the service account in any way. If you do you will get an error that you are not authorized.
Enable Domain-wide Delegation on the service account.
Follow this document to delegate domain-wide authority to your service account:
Delegate domain-wide authority to your service account
When creating the service account credentials (from the downloaded Json) you will need the following scopes for full G Suite management:
"https://www.googleapis.com/auth/admin.directory.group",
"https://www.googleapis.com/auth/admin.directory.user"
Impersonate a user account which creates new credentials. The user account needs to be a G Suite superadmin. This account must have logged into G Suite at least once and accepted the Terms of Service.
Create your client using the credentials from step #5.
Working Python Example:
from googleapiclient.discovery import build
from google.oauth2 import service_account
# This is the service account credentials file
credentials_file = 'google-directory-api.json'
# In this example I only need to send email
credentials = service_account.Credentials.from_service_account_file(
credentials_file,
scopes=['https://www.googleapis.com/auth/gmail.send'])
# This user is a G Suite superadmin
impersonate = 'username#example.com'
credentials = credentials.with_subject(impersonate)
service = build('gmail', 'v1', credentials=credentials)
I think we are going to need to take this in stages. Lets start with your first error and see if that fixes your issue.
Background info
There are several types of clients that you can create in Google developer console. Here are the top three.
Browser client: Used for web applications
Native client (other): used for installed desktop applications
Service account: used for server to server communication.
The clients are different the client.json file you download is different and the code used to authenticated to the authentication server is also different.
Error 1: code / client missmatch
unauthorized_client: Client is unauthorized to retrieve access tokens using this method.
Can mean one of two things. Either you have created a service account client and you are not using service account code to authenticate or you are are using the code to authenticate with a service account but you have not created a service account client. You haven't posted what language you are using or any code so I cant tell you if the code you are using is intended to be used with a service account or not. Your going to have to look into this one a bit.
Check in developer console make sure your client is like this If it is check your code. If it isnt then create a real service account client and try again.

Access user info from lambda

I'm working on a serverless app with aws.
I use AWS Cognito User Pool to manage user : register, login, logout.
Once those users have been confirmed, I use AWS Cognito Identity Pool to get temporary credentials. Then I use those credentials to access the api (the endpoint on my api require AWS_IAM for Auth and call lambda).
All of that work perfectly. But I need to know which user has requested the action. In the lambda I can get the IdentityId from my Identity Pool. But I need to get attributes from my user in User Pool.
So my question is : is there a way to get a user from User Pool using the IdentityId of the Identity attached to it ? Or at least, get the access token ? I know I can send the access token in headers but I would like to only depend on the AWS_IAM auth.
Getting from a federated identity_id back to the user pool user is tricky because there's no guarantee it is a user pool user (it could well be someone from Facebook, or even an unauthenticated user- depending on your configuration).
Given an IdentityId you can use identity:GetOpenIdToken to get a valid OpenId token (you can ignore the logins part of the request if you are just using UserPools).
You can then use this token against the userpools:GetUser end point.
There's a few pitfalls here, like ensuring you authenticate with a scope that allows you to see all the attributes you care about. If you haven't, then you'll need to use the username returned with userpools:AdminGetUser to get the full user profile.

Cognito, API Gateway and Lambda assuming the identity of the cognito user

I’m building a React app, using API Gateway, lambda and cognito (basically starting from the https://serverless-stack.com tutorial). I would like to setup fine grained access control to my DynamoDb (i.e. through IAM policies that restrict access to DynamoDb tables based upon the logged-in user - like https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_rows.html)
AFAIK, a lambda function assumes a service role, as defined in the serverless.yml file, that has in itself nothing to do with the AIM policy that is attached to the logged in cognito user. I know that using an aim_authorizer, I can get info on the logged in user.
My question: is it possible to have the lambda do AWS calls on behalf of the given cognito user, thus honoring the IAM policies attached to that user? (a bit similar as to how the serverless-stack tutorial interacts with S3)
All suggestions welcome.
You can explicitly specify to any AWS client library which credentials to use in order to sign requests (by default they are taken from the runtime environment):
import { DocumentClient } from 'aws-sdk/clients/dynamodb';
const client = new DocumentClient({
credentials: ...
});
Those security credentials are obtain via STS. There are various scenarios how to get a hold of the user's identity to obtain credentials, but usually you would either assumeRole, if you have an arn of a role, or assumeRoleWithWebIdentity, if there is an actual user that did a flow of OpenID Connect:
import { Credentials, STS } from 'aws-sdk';
const sts = new STS();
const stsResponse = await sts.assumeRole({ RoleArn: 'can-be-cognito-group-arn' }).promise();
// or
// const stsResponse = await sts.assumeRoleWithWebIdentity({ WebIdentityToken: 'open-id-token' }).promise();
const credentials = new Credentials(
response.Credentials.AccessKeyId,
response.Credentials.SecretAccessKey,
response.Credentials.SessionToken);

Resources