Page updated Jan 16, 2024

Set up admin actions

Admin Actions allow you to execute queries and operations against users and groups in your Cognito user pool.

For example, the ability to list all users in a Cognito User Pool may provide useful for the administrative panel of an app if the logged-in user is a member of a specific Group called "Admins".

This is an advanced feature that is not recommended without an understanding of the underlying architecture. The associated infrastructure which is created is a base designed for you to customize for your specific business needs. We recommend removing any functionality which your app does not require.

The Amplify CLI can setup a REST endpoint with secure access to a Lambda function running with limited permissions to the User Pool if you wish to have these capabilities in your application, and you can choose to expose the actions to all users with a valid account or restrict to a specific User Pool Group.

Enable Admin Queries

amplify add auth
1amplify add auth

Select the option to go through Manual configuration.

Do you want to use the default authentication and security configuration? (Use arrow keys) Default configuration Default configuration with Social Provider (Federation) ❯ Manual configuration I want to learn more.
1Do you want to use the default authentication and security configuration? (Use arrow keys)
2 Default configuration
3 Default configuration with Social Provider (Federation)
4❯ Manual configuration
5 I want to learn more.

Go through the rest of the configuration steps until you reach the following prompts:

? Do you want to add User Pool Groups? Yes ? Provide a name for your user pool group: Admins ? Do you want to add another User Pool Group No ✔ Sort the user pool groups in order of preference · Admins ? Do you want to add an admin queries API? Yes ? Do you want to restrict access to the admin queries API to a specific Group? Yes ? Select the group to restrict access with: (Use arrow keys) ❯ Admins Enter a custom group
1? Do you want to add User Pool Groups? Yes
2? Provide a name for your user pool group: Admins
3? Do you want to add another User Pool Group No
4✔ Sort the user pool groups in order of preference · Admins
5? Do you want to add an admin queries API? Yes
6? Do you want to restrict access to the admin queries API to a specific Group? Yes
7? Select the group to restrict access with: (Use arrow keys)
8❯ Admins
9 Enter a custom group

Continue with the rest of the prompts to finish the configuration.

amplify update auth
1amplify update auth

Select the option to Create or update Admin queries API.

What do you want to do? Create or update Admin queries API ? Do you want to restrict access to the admin queries API to a specific Group Yes ? Select the group to restrict access with: (Use arrow keys) ❯ Admins Enter a custom group
1What do you want to do? Create or update Admin queries API
2? Do you want to restrict access to the admin queries API to a specific Group Yes
3? Select the group to restrict access with: (Use arrow keys)
4❯ Admins
5 Enter a custom group

If you don't have any User Pool Groups, you will need to select Enter a custom group.

When ready, run amplify push to deploy the changes.

This will configure an API Gateway endpoint with a Cognito Authorizer that accepts an Access Token, which is used by a Lambda function to perform actions against the User Pool. The function is example code which you can use to remove, add, or alter functionality based on your business case by editing it in the amplify/backend/function/AdminQueriesXXX/src directory and running an amplify push to deploy your changes. If you choose to restrict actions to a specific Group, custom middleware in the function will prevent any actions unless the user is a member of that Group.

Admin Queries API

The default routes and their functions, HTTP methods, and expected parameters are below

  • addUserToGroup: Adds a user to a specific Group. Expects username and groupname in the POST body.
  • removeUserFromGroup: Removes a user from a specific Group. Expects username and groupname in the POST body.
  • confirmUserSignUp: Confirms a users signup. Expects username in the POST body.
  • disableUser: Disables a user. Expects username in the POST body.
  • enableUser: Enables a user. Expects username in the POST body.
  • getUser: Gets specific user details. Expects username as a GET query string.
  • listUsers: Lists all users in the current Cognito User Pool. You can provide an OPTIONAL limit (between 0 and 60) as a GET query string, which returns a NextToken that can be provided as a token query string for pagination.
  • listGroups: Lists all groups in the current Cognito User Pool. You can provide an OPTIONAL limit (between 0 and 60) as a GET query string, which returns a NextToken that can be provided as a token query string for pagination.
  • listGroupsForUser: Lists groups to which current user belongs to. Expects username as a GET query string. You can provide an OPTIONAL limit (between 0 and 60) as a GET query string, which returns a NextToken that can be provided as a token query string for pagination.
  • listUsersInGroup: Lists users that belong to a specific group. Expects groupname as a GET query string. You can provide an OPTIONAL limit (between 0 and 60) as a GET query string, which returns a NextToken that can be provided as a token query string for pagination.
  • signUserOut: Signs a user out from User Pools, but only if the call is originating from that user. Expects username in the POST body.

Example

To leverage this functionality in your app you would call the appropriate route from Amplify.API after signing in. The following example adds the user "richard" to the Editors Group and then list all members of the Editors Group with a pagination limit of 10:

import React from 'react' import { Amplify } from 'aws-amplify'; import { fetchAuthSession } from 'aws-amplify/auth'; import { post } from 'aws-amplify/api' import { withAuthenticator } from '@aws-amplify/ui-react'; import '@aws-amplify/ui-react/styles.css'; import amplifyconfig from './amplifyconfiguration.json'; Amplify.configure(amplifyconfig); const client = generateClient() async function addToGroup() { let apiName = 'AdminQueries'; let path = '/addUserToGroup'; let options = { body: { "username" : "richard", "groupname": "Editors" }, headers: { 'Content-Type' : 'application/json', Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` } } return post({apiName, path, options}); } async function listEditors(limit){ let apiName = 'AdminQueries'; let path = '/listUsersInGroup'; let options = { queryStringParameters: { "groupname": "Editors", "limit": limit, }, headers: { 'Content-Type' : 'application/json', Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}` } } const response = await get({apiName, path, options}); return response; } function App() { return ( <div className="App"> <button onClick={addToGroup}>Add to Group</button> <button onClick={() => listEditors(10)}>List Editors</button> </div> ); } export default withAuthenticator(App);
1import React from 'react'
2import { Amplify } from 'aws-amplify';
3import { fetchAuthSession } from 'aws-amplify/auth';
4import { post } from 'aws-amplify/api'
5import { withAuthenticator } from '@aws-amplify/ui-react';
6import '@aws-amplify/ui-react/styles.css';
7
8import amplifyconfig from './amplifyconfiguration.json';
9Amplify.configure(amplifyconfig);
10
11const client = generateClient()
12
13async function addToGroup() {
14 let apiName = 'AdminQueries';
15 let path = '/addUserToGroup';
16 let options = {
17 body: {
18 "username" : "richard",
19 "groupname": "Editors"
20 },
21 headers: {
22 'Content-Type' : 'application/json',
23 Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}`
24 }
25 }
26 return post({apiName, path, options});
27}
28
29async function listEditors(limit){
30 let apiName = 'AdminQueries';
31 let path = '/listUsersInGroup';
32 let options = {
33 queryStringParameters: {
34 "groupname": "Editors",
35 "limit": limit,
36 },
37 headers: {
38 'Content-Type' : 'application/json',
39 Authorization: `${(await fetchAuthSession()).tokens.accessToken.payload}`
40 }
41 }
42 const response = await get({apiName, path, options});
43 return response;
44}
45
46function App() {
47 return (
48 <div className="App">
49 <button onClick={addToGroup}>Add to Group</button>
50 <button onClick={() => listEditors(10)}>List Editors</button>
51 </div>
52 );
53}
54
55export default withAuthenticator(App);
  1. Initialize Amplify API. Refer to Set up Amplify REST API for more details.

You should have the initialization code including the imports:

import Amplify import AWSCognitoAuthPlugin import AWSAPIPlugin
1import Amplify
2import AWSCognitoAuthPlugin
3import AWSAPIPlugin

and code that adds AWSCognitoAuthPlugin and AWSAPIPlugin before configuring Amplify.

try Amplify.add(plugin: AWSCognitoAuthPlugin()) try Amplify.add(plugin: AWSAPIPlugin()) try Amplify.configure()
1try Amplify.add(plugin: AWSCognitoAuthPlugin())
2try Amplify.add(plugin: AWSAPIPlugin())
3try Amplify.configure()
  1. Sign in using Amplify.Auth. See Amplify.Auth to learn more about signing up and signing in a user.

  2. Use the following in your app to add a user to the Group.

func addToGroup(username: String, groupName: String) async { let path = "/addUserToGroup" let body = "{\"username\":\"\(username)\",\"groupname\":\"\(groupName)\"}".data(using: .utf8) let request = RESTRequest(path: path, body: body) do { let data = try await Amplify.API.post(request: request) print("Response Body: \(String(decoding: data, as: UTF8.self))") } catch { if case let .httpStatusError(statusCode, response) = error as? APIError, let awsResponse = response as? AWSHTTPURLResponse, let responseBody = awsResponse.body { print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") } } } await addToGroup(username: "richard", groupName: "Editors")
1func addToGroup(username: String, groupName: String) async {
2 let path = "/addUserToGroup"
3 let body = "{\"username\":\"\(username)\",\"groupname\":\"\(groupName)\"}".data(using: .utf8)
4 let request = RESTRequest(path: path, body: body)
5 do {
6 let data = try await Amplify.API.post(request: request)
7 print("Response Body: \(String(decoding: data, as: UTF8.self))")
8 } catch {
9 if case let .httpStatusError(statusCode, response) = error as? APIError,
10 let awsResponse = response as? AWSHTTPURLResponse,
11 let responseBody = awsResponse.body {
12 print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))")
13 }
14 }
15}
16
17await addToGroup(username: "richard", groupName: "Editors")
  1. Use the following to list the users in the Group.
func listEditors(groupName: String, limit: Int, nextToken: String? = nil) async { let path = "/listUsersInGroup" var query = [ "groupname": groupName, "limit": String(limit) ] if let nextToken = nextToken { query["token"] = nextToken } let request = RESTRequest(path: path, queryParameters: query, body: nil) do { let data = try await Amplify.API.get(request: request) print("Response Body: \(String(decoding: data, as: UTF8.self))") } catch { if case let .httpStatusError(statusCode, response) = error as? APIError, let awsResponse = response as? AWSHTTPURLResponse, let responseBody = awsResponse.body { print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))") } } } await listEditors(groupName: "Editors", limit: 10)
1func listEditors(groupName: String, limit: Int, nextToken: String? = nil) async {
2 let path = "/listUsersInGroup"
3 var query = [
4 "groupname": groupName,
5 "limit": String(limit)
6 ]
7 if let nextToken = nextToken {
8 query["token"] = nextToken
9 }
10
11 let request = RESTRequest(path: path, queryParameters: query, body: nil)
12 do {
13 let data = try await Amplify.API.get(request: request)
14 print("Response Body: \(String(decoding: data, as: UTF8.self))")
15 } catch {
16 if case let .httpStatusError(statusCode, response) = error as? APIError,
17 let awsResponse = response as? AWSHTTPURLResponse,
18 let responseBody = awsResponse.body {
19 print("StatusCode: \(statusCode) Response Body: \(String(decoding: responseBody, as: UTF8.self))")
20 }
21 }
22}
23
24await listEditors(groupName: "Editors", limit: 10)

Note: Cognito User Pool with HostedUI

The Admin Queries API configuration in amplifyconfiguration.json will have the endpoint's authorization type set to AMAZON_COGNITO_USER_POOLS. With this authorization type, Amplify.API will perform the request with the access token. However, when using HostedUI, the app may get unauthorized responses despite being signed in, and will require using the ID Token. Set the authorizationType to "NONE" and add a custom interceptor to return the ID Token.

{ "awsAPIPlugin": { "[YOUR-RESTENDPOINT-NAME]": { "endpointType": "REST", "endpoint": "[YOUR-REST-ENDPOINT]", "region": "[REGION]", "authorizationType": "NONE" } } }
1{
2 "awsAPIPlugin": {
3 "[YOUR-RESTENDPOINT-NAME]": {
4 "endpointType": "REST",
5 "endpoint": "[YOUR-REST-ENDPOINT]",
6 "region": "[REGION]",
7 "authorizationType": "NONE"
8 }
9 }
10}

If you perform additional updates to your resources using Amplify CLI, the authorizationType will be reverted back to AMAZON_COGNITO_USER_POOLS. Make sure to update this back to NONE.

Add a custom interceptor to the API

try Amplify.configure() try Amplify.API.add(interceptor: MyCustomInterceptor(), for: "[YOUR-RESTENDPOINT-NAME]")
1try Amplify.configure()
2try Amplify.API.add(interceptor: MyCustomInterceptor(), for: "[YOUR-RESTENDPOINT-NAME]")

Set up the custom interceptor to return the ID token for the request.

import Amplify import AWSPluginsCore class MyCustomInterceptor: URLRequestInterceptor { func latestAuthToken() async throws -> String { guard let session = try await Amplify.Auth.fetchAuthSession() as? AuthCognitoTokensProvider else { throw AuthError.unknown("Could not retrieve Cognito token") } let tokens = try session.getCognitoTokens().get() return tokens.idToken } func intercept(_ request: URLRequest) async throws -> URLRequest { var request = request do { let token = try await latestAuthToken() request.setValue(token, forHTTPHeaderField: "authorization") } catch { throw APIError.operationError("Failed to retrieve Cognito UserPool token.", "", error) } return request } }
1import Amplify
2import AWSPluginsCore
3
4class MyCustomInterceptor: URLRequestInterceptor {
5 func latestAuthToken() async throws -> String {
6 guard let session = try await Amplify.Auth.fetchAuthSession() as? AuthCognitoTokensProvider else {
7 throw AuthError.unknown("Could not retrieve Cognito token")
8 }
9
10 let tokens = try session.getCognitoTokens().get()
11 return tokens.idToken
12 }
13
14 func intercept(_ request: URLRequest) async throws -> URLRequest {
15 var request = request
16 do {
17 let token = try await latestAuthToken()
18 request.setValue(token, forHTTPHeaderField: "authorization")
19 } catch {
20 throw APIError.operationError("Failed to retrieve Cognito UserPool token.", "", error)
21 }
22 return request
23 }
24}

Adding Admin Actions

To add additional admin actions that are not included by default but are enabled by Amazon Cognito, you will need to update the Lambda function code that is generated for you. The change will include adding a route handler for the action and creating a route for it. You will then associate the route handler to the route within the Express app.

Below is an example of how to add an admin action that will allow you to update a user's attributes.

async function updateUserAttributes(username, attributes) { const params = { Username: username, UserAttributes: attributes, UserPoolId: 'STRING_VALUE', }; console.log(`Attempting to update ${username} attributes`); try { await cognitoIdentityServiceProvider.adminUpdateUserAttributes(params).promise(); console.log(`Success updating ${username} attributes`); return { message: `Success updating ${username} attributes`, }; } catch (err) { console.log(err); throw err; } }
1async function updateUserAttributes(username, attributes) {
2
3 const params = {
4 Username: username,
5 UserAttributes: attributes,
6 UserPoolId: 'STRING_VALUE',
7 };
8
9 console.log(`Attempting to update ${username} attributes`);
10
11 try {
12 await cognitoIdentityServiceProvider.adminUpdateUserAttributes(params).promise();
13 console.log(`Success updating ${username} attributes`);
14 return {
15 message: `Success updating ${username} attributes`,
16 };
17 } catch (err) {
18 console.log(err);
19 throw err;
20 }
21}

Once the route handler is defined, you will then add a route with the correct HTTP method to the Express app and associate the route handler to the route. Be sure to make the route unique.

Below is an example of how you can add a POST route named /updateUserAttributes and associate the above route handler to it.

app.post('/updateUserAttributes', async (req, res, next) => { if (!req.body.username || !req.body.attributes) { const err = new Error('username and attributes are required'); err.statusCode = 400; return next(err); } try { const response = await updateUserAttributes(req.body.username, req.body.attributes); res.status(200).json(response); } catch (err) { next(err); } });
1app.post('/updateUserAttributes', async (req, res, next) => {
2 if (!req.body.username || !req.body.attributes) {
3 const err = new Error('username and attributes are required');
4 err.statusCode = 400;
5 return next(err);
6 }
7
8 try {
9 const response = await updateUserAttributes(req.body.username, req.body.attributes);
10 res.status(200).json(response);
11 } catch (err) {
12 next(err);
13 }
14});