diff --git a/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java b/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java
index bfe6e600c9..01f7ad9f3d 100644
--- a/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java
+++ b/src/integrationTest/java/org/opensearch/security/privileges/ActionPrivilegesTest.java
@@ -39,7 +39,6 @@
 import org.opensearch.core.common.unit.ByteSizeUnit;
 import org.opensearch.core.common.unit.ByteSizeValue;
 import org.opensearch.security.action.apitokens.ApiToken;
-import org.opensearch.security.action.apitokens.ApiTokenRepository;
 import org.opensearch.security.action.apitokens.Permissions;
 import org.opensearch.security.resolver.IndexResolverReplacer;
 import org.opensearch.security.securityconf.FlattenedActionGroups;
@@ -50,9 +49,8 @@
 import org.opensearch.security.user.User;
 import org.opensearch.security.util.MockIndexMetadataBuilder;
 
-import org.mockito.Mockito;
-
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.opensearch.security.privileges.ActionPrivilegesTest.IndexPrivileges.IndicesAndAliases.resolved;
 import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isAllowed;
 import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isForbidden;
 import static org.opensearch.security.privileges.PrivilegeEvaluatorResponseMatcher.isPartiallyOk;
@@ -342,6 +340,7 @@ public void apiToken_succeedsWithActionGroupsExapnded() throws Exception {
                 "apitoken:" + token,
                 new Permissions(List.of("CLUSTER_ALL"), List.of())
             );
+
             // Explicit succeeds
             assertThat(subject.hasExplicitClusterPrivilege(context, "cluster:whatever"), isAllowed());
             // Not explicit succeeds
@@ -1152,7 +1151,6 @@ static RoleBasedPrivilegesEvaluationContext ctx(String... roles) {
     static RoleBasedPrivilegesEvaluationContext ctxWithUserName(String userName, String... roles) {
         User user = new User(userName);
         user.addAttributes(ImmutableMap.of("attrs.dept_no", "a11"));
-        ApiTokenRepository mockRepository = Mockito.mock(ApiTokenRepository.class);
         return new RoleBasedPrivilegesEvaluationContext(
             user,
             ImmutableSet.copyOf(roles),
diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
index 1d5091ca04..c68c4fb610 100644
--- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
+++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java
@@ -259,7 +259,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
     private volatile UserService userService;
     private volatile RestLayerPrivilegesEvaluator restLayerEvaluator;
     private volatile ConfigurationRepository cr;
-    private volatile ApiTokenRepository ar;
+    private volatile ApiTokenRepository apiTokenRepository;
     private volatile AdminDNs adminDns;
     private volatile ClusterService cs;
     private volatile AtomicReference<DiscoveryNode> localNode = new AtomicReference<>();
@@ -649,7 +649,21 @@ public List<RestHandler> getRestHandlers(
                     )
                 );
                 handlers.add(new CreateOnBehalfOfTokenAction(tokenManager));
-                handlers.add(new ApiTokenAction(ar));
+                handlers.add(
+                    new ApiTokenAction(
+                        Objects.requireNonNull(threadPool),
+                        cr,
+                        evaluator,
+                        settings,
+                        adminDns,
+                        auditLog,
+                        configPath,
+                        principalExtractor,
+                        apiTokenRepository,
+                        cs,
+                        indexNameExpressionResolver
+                    )
+                );
                 handlers.addAll(
                     SecurityRestApiActions.getHandler(
                         settings,
@@ -1111,7 +1125,7 @@ public Collection<Object> createComponents(
         final XFFResolver xffResolver = new XFFResolver(threadPool);
         backendRegistry = new BackendRegistry(settings, adminDns, xffResolver, auditLog, threadPool);
         tokenManager = new SecurityTokenManager(cs, threadPool, userService);
-        ar = new ApiTokenRepository(localClient, clusterService, tokenManager);
+        apiTokenRepository = new ApiTokenRepository(localClient, clusterService, tokenManager);
 
         final CompatConfig compatConfig = new CompatConfig(environment, transportPassiveAuthSetting);
 
@@ -1128,7 +1142,7 @@ public Collection<Object> createComponents(
             cih,
             irr,
             namedXContentRegistry.get(),
-            ar
+            apiTokenRepository
         );
 
         dlsFlsBaseContext = new DlsFlsBaseContext(evaluator, threadPool.getThreadContext(), adminDns);
@@ -1170,7 +1184,7 @@ public Collection<Object> createComponents(
             configPath,
             compatConfig
         );
-        dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih, passwordHasher, ar);
+        dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih, passwordHasher, apiTokenRepository);
         dcf.registerDCFListener(backendRegistry);
         dcf.registerDCFListener(compatConfig);
         dcf.registerDCFListener(irr);
@@ -1220,7 +1234,7 @@ public Collection<Object> createComponents(
         components.add(dcf);
         components.add(userService);
         components.add(passwordHasher);
-        components.add(ar);
+        components.add(apiTokenRepository);
 
         components.add(sslSettingsManager);
         if (isSslCertReloadEnabled(settings) && sslCertificatesHotReloadEnabled(settings)) {
@@ -2143,7 +2157,11 @@ public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings sett
             ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX
         );
         final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index");
-        return Collections.singletonList(systemIndexDescriptor);
+        final SystemIndexDescriptor apiTokenSystemIndexDescriptor = new SystemIndexDescriptor(
+            ConfigConstants.OPENSEARCH_API_TOKENS_INDEX,
+            "Security API token index"
+        );
+        return List.of(systemIndexDescriptor, apiTokenSystemIndexDescriptor);
     }
 
     @Override
diff --git a/src/main/java/org/opensearch/security/action/apitokens/ApiTokenAction.java b/src/main/java/org/opensearch/security/action/apitokens/ApiTokenAction.java
index 0b6a7b320a..eddafd79ee 100644
--- a/src/main/java/org/opensearch/security/action/apitokens/ApiTokenAction.java
+++ b/src/main/java/org/opensearch/security/action/apitokens/ApiTokenAction.java
@@ -12,6 +12,7 @@
 package org.opensearch.security.action.apitokens;
 
 import java.io.IOException;
+import java.nio.file.Path;
 import java.time.Instant;
 import java.util.Collections;
 import java.util.List;
@@ -24,14 +25,29 @@
 import org.apache.logging.log4j.Logger;
 
 import org.opensearch.client.node.NodeClient;
+import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
+import org.opensearch.cluster.service.ClusterService;
+import org.opensearch.common.settings.Settings;
+import org.opensearch.common.util.concurrent.ThreadContext;
 import org.opensearch.core.action.ActionListener;
 import org.opensearch.core.rest.RestStatus;
 import org.opensearch.core.xcontent.XContentBuilder;
 import org.opensearch.rest.BaseRestHandler;
 import org.opensearch.rest.BytesRestResponse;
 import org.opensearch.rest.RestChannel;
-import org.opensearch.rest.RestHandler;
 import org.opensearch.rest.RestRequest;
+import org.opensearch.security.auditlog.AuditLog;
+import org.opensearch.security.configuration.AdminDNs;
+import org.opensearch.security.configuration.ConfigurationRepository;
+import org.opensearch.security.dlic.rest.api.Endpoint;
+import org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator;
+import org.opensearch.security.dlic.rest.api.RestApiPrivilegesEvaluator;
+import org.opensearch.security.dlic.rest.api.SecurityApiDependencies;
+import org.opensearch.security.dlic.rest.support.Utils;
+import org.opensearch.security.privileges.PrivilegesEvaluator;
+import org.opensearch.security.ssl.transport.PrincipalExtractor;
+import org.opensearch.security.support.ConfigConstants;
+import org.opensearch.threadpool.ThreadPool;
 
 import static org.opensearch.rest.RestRequest.Method.DELETE;
 import static org.opensearch.rest.RestRequest.Method.GET;
@@ -44,23 +60,57 @@
 import static org.opensearch.security.action.apitokens.ApiToken.INDEX_PERMISSIONS_FIELD;
 import static org.opensearch.security.action.apitokens.ApiToken.NAME_FIELD;
 import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix;
+import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED;
 import static org.opensearch.security.util.ParsingUtils.safeMapList;
 import static org.opensearch.security.util.ParsingUtils.safeStringList;
 
 public class ApiTokenAction extends BaseRestHandler {
-    private ApiTokenRepository apiTokenRepository;
+    private final ApiTokenRepository apiTokenRepository;
     public Logger log = LogManager.getLogger(this.getClass());
-
-    private static final List<RestHandler.Route> ROUTES = addRoutesPrefix(
-        ImmutableList.of(
-            new RestHandler.Route(POST, "/apitokens"),
-            new RestHandler.Route(DELETE, "/apitokens"),
-            new RestHandler.Route(GET, "/apitokens")
-        )
+    private final ThreadPool threadPool;
+    private final ConfigurationRepository configurationRepository;
+    private final PrivilegesEvaluator privilegesEvaluator;
+    private final SecurityApiDependencies securityApiDependencies;
+    private final ClusterService clusterService;
+    private final IndexNameExpressionResolver indexNameExpressionResolver;
+
+    private static final List<Route> ROUTES = addRoutesPrefix(
+        ImmutableList.of(new Route(POST, "/apitokens"), new Route(DELETE, "/apitokens"), new Route(GET, "/apitokens"))
     );
 
-    public ApiTokenAction(ApiTokenRepository apiTokenRepository) {
+    public ApiTokenAction(
+        ThreadPool threadpool,
+        ConfigurationRepository configurationRepository,
+        PrivilegesEvaluator privilegesEvaluator,
+        Settings settings,
+        AdminDNs adminDns,
+        AuditLog auditLog,
+        Path configPath,
+        PrincipalExtractor principalExtractor,
+        ApiTokenRepository apiTokenRepository,
+        ClusterService clusterService,
+        IndexNameExpressionResolver indexNameExpressionResolver
+    ) {
         this.apiTokenRepository = apiTokenRepository;
+        this.threadPool = threadpool;
+        this.configurationRepository = configurationRepository;
+        this.privilegesEvaluator = privilegesEvaluator;
+        this.securityApiDependencies = new SecurityApiDependencies(
+            adminDns,
+            configurationRepository,
+            privilegesEvaluator,
+            new RestApiPrivilegesEvaluator(settings, adminDns, privilegesEvaluator, principalExtractor, configPath, threadPool),
+            new RestApiAdminPrivilegesEvaluator(
+                threadPool.getThreadContext(),
+                privilegesEvaluator,
+                adminDns,
+                settings.getAsBoolean(SECURITY_RESTAPI_ADMIN_ENABLED, false)
+            ),
+            auditLog,
+            settings
+        );
+        this.clusterService = clusterService;
+        this.indexNameExpressionResolver = indexNameExpressionResolver;
     }
 
     @Override
@@ -69,22 +119,28 @@ public String getName() {
     }
 
     @Override
-    public List<RestHandler.Route> routes() {
+    public List<Route> routes() {
         return ROUTES;
     }
 
     @Override
     protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        // TODO: Authorize this API properly
-        switch (request.method()) {
-            case POST:
-                return handlePost(request, client);
-            case DELETE:
-                return handleDelete(request, client);
-            case GET:
-                return handleGet(request, client);
-            default:
-                throw new IllegalArgumentException(request.method() + " not supported");
+        authorizeSecurityAccess(request);
+        return doPrepareRequest(request, client);
+    }
+
+    RestChannelConsumer doPrepareRequest(RestRequest request, NodeClient client) {
+        final var originalUserAndRemoteAddress = Utils.userAndRemoteAddressFrom(client.threadPool().getThreadContext());
+        try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {
+            client.threadPool()
+                .getThreadContext()
+                .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUserAndRemoteAddress.getLeft());
+            return switch (request.method()) {
+                case POST -> handlePost(request, client);
+                case DELETE -> handleDelete(request, client);
+                case GET -> handleGet(request, client);
+                default -> throw new IllegalArgumentException(request.method() + " not supported");
+            };
         }
     }
 
@@ -119,8 +175,6 @@ private RestChannelConsumer handleGet(RestRequest request, NodeClient client) {
 
     private RestChannelConsumer handlePost(RestRequest request, NodeClient client) {
         return channel -> {
-            final XContentBuilder builder = channel.newBuilder();
-            BytesRestResponse response;
             try {
                 final Map<String, Object> requestBody = request.contentOrSourceParamParser().map();
                 validateRequestParameters(requestBody);
@@ -245,8 +299,6 @@ void validateIndexPermissionsList(List<Map<String, Object>> indexPermsList) {
 
     private RestChannelConsumer handleDelete(RestRequest request, NodeClient client) {
         return channel -> {
-            final XContentBuilder builder = channel.newBuilder();
-            BytesRestResponse response;
             try {
                 final Map<String, Object> requestBody = request.contentOrSourceParamParser().map();
 
@@ -295,4 +347,11 @@ private void sendErrorResponse(RestChannel channel, RestStatus status, String er
         }
     }
 
+    protected void authorizeSecurityAccess(RestRequest request) throws IOException {
+        // Check if user has security API access
+        if (!(securityApiDependencies.restApiAdminPrivilegesEvaluator().isCurrentUserAdminFor(Endpoint.APITOKENS)
+            || securityApiDependencies.restApiPrivilegesEvaluator().checkAccessPermissions(request, Endpoint.APITOKENS) == null)) {
+            throw new SecurityException("User does not have required security API access");
+        }
+    }
 }
diff --git a/src/main/java/org/opensearch/security/action/apitokens/ApiTokenIndexHandler.java b/src/main/java/org/opensearch/security/action/apitokens/ApiTokenIndexHandler.java
index 488229a319..9145ee4bb1 100644
--- a/src/main/java/org/opensearch/security/action/apitokens/ApiTokenIndexHandler.java
+++ b/src/main/java/org/opensearch/security/action/apitokens/ApiTokenIndexHandler.java
@@ -26,7 +26,6 @@
 import org.opensearch.action.search.SearchResponse;
 import org.opensearch.client.Client;
 import org.opensearch.cluster.service.ClusterService;
-import org.opensearch.common.util.concurrent.ThreadContext;
 import org.opensearch.common.xcontent.XContentFactory;
 import org.opensearch.common.xcontent.XContentType;
 import org.opensearch.core.action.ActionListener;
@@ -41,7 +40,6 @@
 import org.opensearch.index.reindex.DeleteByQueryRequest;
 import org.opensearch.search.SearchHit;
 import org.opensearch.search.builder.SearchSourceBuilder;
-import org.opensearch.security.dlic.rest.support.Utils;
 import org.opensearch.security.support.ConfigConstants;
 
 import static org.opensearch.security.action.apitokens.ApiToken.NAME_FIELD;
@@ -57,14 +55,8 @@ public ApiTokenIndexHandler(Client client, ClusterService clusterService) {
         this.clusterService = clusterService;
     }
 
-    public String indexTokenMetadata(ApiToken token) {
-        // TODO: move this out of index handler class, potentially create a layer in between baseresthandler and abstractapiaction which can
-        // abstract this complexity away
-        final var originalUserAndRemoteAddress = Utils.userAndRemoteAddressFrom(client.threadPool().getThreadContext());
-        try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {
-            client.threadPool()
-                .getThreadContext()
-                .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUserAndRemoteAddress.getLeft());
+    public void indexTokenMetadata(ApiToken token) {
+        try {
 
             XContentBuilder builder = XContentFactory.jsonBuilder();
             String jsonString = token.toXContent(builder, ToXContent.EMPTY_PARAMS).toString();
@@ -77,10 +69,7 @@ public String indexTokenMetadata(ApiToken token) {
                 LOGGER.error(failResponse.getMessage());
                 LOGGER.info("Failed to create {} entry.", ConfigConstants.OPENSEARCH_API_TOKENS_INDEX);
             });
-
             client.index(request, irListener);
-            return token.getName();
-
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
@@ -88,32 +77,21 @@ public String indexTokenMetadata(ApiToken token) {
     }
 
     public void deleteToken(String name) throws ApiTokenException {
-        final var originalUserAndRemoteAddress = Utils.userAndRemoteAddressFrom(client.threadPool().getThreadContext());
-        try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {
-            client.threadPool()
-                .getThreadContext()
-                .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUserAndRemoteAddress.getLeft());
-            DeleteByQueryRequest request = new DeleteByQueryRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX).setQuery(
-                QueryBuilders.matchQuery(NAME_FIELD, name)
-            ).setRefresh(true);
+        DeleteByQueryRequest request = new DeleteByQueryRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX).setQuery(
+            QueryBuilders.matchQuery(NAME_FIELD, name)
+        ).setRefresh(true);
 
-            BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, request).actionGet();
+        BulkByScrollResponse response = client.execute(DeleteByQueryAction.INSTANCE, request).actionGet();
 
-            long deletedDocs = response.getDeleted();
+        long deletedDocs = response.getDeleted();
 
-            if (deletedDocs == 0) {
-                throw new ApiTokenException("No token found with name " + name);
-            }
-            LOGGER.info("Deleted " + deletedDocs + " documents");
+        if (deletedDocs == 0) {
+            throw new ApiTokenException("No token found with name " + name);
         }
     }
 
     public Map<String, ApiToken> getTokenMetadatas() {
-        final var originalUserAndRemoteAddress = Utils.userAndRemoteAddressFrom(client.threadPool().getThreadContext());
-        try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {
-            client.threadPool()
-                .getThreadContext()
-                .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUserAndRemoteAddress.getLeft());
+        try {
             SearchRequest searchRequest = new SearchRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX);
             searchRequest.source(new SearchSourceBuilder());
 
@@ -145,24 +123,12 @@ public Boolean apiTokenIndexExists() {
     }
 
     public void createApiTokenIndexIfAbsent() {
-        // TODO: Decide if this should be done at bootstrap
         if (!apiTokenIndexExists()) {
-            final var originalUserAndRemoteAddress = Utils.userAndRemoteAddressFrom(client.threadPool().getThreadContext());
-            try (final ThreadContext.StoredContext ctx = client.threadPool().getThreadContext().stashContext()) {
-                client.threadPool()
-                    .getThreadContext()
-                    .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUserAndRemoteAddress.getLeft());
-                final Map<String, Object> indexSettings = ImmutableMap.of(
-                    "index.number_of_shards",
-                    1,
-                    "index.auto_expand_replicas",
-                    "0-all"
-                );
-                final CreateIndexRequest createIndexRequest = new CreateIndexRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX).settings(
-                    indexSettings
-                );
-                client.admin().indices().create(createIndexRequest);
-            }
+            final Map<String, Object> indexSettings = ImmutableMap.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all");
+            final CreateIndexRequest createIndexRequest = new CreateIndexRequest(ConfigConstants.OPENSEARCH_API_TOKENS_INDEX).settings(
+                indexSettings
+            );
+            client.admin().indices().create(createIndexRequest);
         }
     }
 
diff --git a/src/main/java/org/opensearch/security/action/apitokens/ApiTokenRepository.java b/src/main/java/org/opensearch/security/action/apitokens/ApiTokenRepository.java
index b1f99bdbd6..850fad0b66 100644
--- a/src/main/java/org/opensearch/security/action/apitokens/ApiTokenRepository.java
+++ b/src/main/java/org/opensearch/security/action/apitokens/ApiTokenRepository.java
@@ -89,7 +89,6 @@ public String createApiToken(
         Long expiration
     ) {
         apiTokenIndexHandler.createApiTokenIndexIfAbsent();
-        // TODO: Add validation on whether user is creating a token with a subset of their permissions
         ExpiringBearerAuthToken token = securityTokenManager.issueApiToken(name, expiration);
         ApiToken apiToken = new ApiToken(name, clusterPermissions, indexPermissions, expiration);
         apiTokenIndexHandler.indexTokenMetadata(apiToken);
diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java
index 9a16cd8bfd..5b90f46f83 100644
--- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java
+++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java
@@ -126,7 +126,6 @@ protected AbstractAuditLog(
             ConfigConstants.SECURITY_CONFIG_INDEX_NAME,
             ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX
         );
-        // TODO: support custom api tokens index?
         this.securityIndicesMatcher = WildcardMatcher.from(
             List.of(
                 settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX),
diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java b/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java
index ecc9dcbc59..d5555b445c 100644
--- a/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java
+++ b/src/main/java/org/opensearch/security/dlic/rest/api/Endpoint.java
@@ -30,5 +30,6 @@ public enum Endpoint {
     WHITELIST,
     ALLOWLIST,
     NODESDN,
-    SSL;
+    SSL,
+    APITOKENS;
 }
diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java
index faa0217db2..768f9d2f70 100644
--- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java
+++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java
@@ -70,6 +70,7 @@ default String build() {
         .put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES))
         .put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING))
         .put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS))
+        .put(Endpoint.APITOKENS, action -> buildEndpointPermission(Endpoint.APITOKENS))
         .put(Endpoint.SSL, action -> buildEndpointActionPermission(Endpoint.SSL, action))
         .build();
 
diff --git a/src/main/java/org/opensearch/security/http/ApiTokenAuthenticator.java b/src/main/java/org/opensearch/security/http/ApiTokenAuthenticator.java
index 0a8e3466d7..50b80ad522 100644
--- a/src/main/java/org/opensearch/security/http/ApiTokenAuthenticator.java
+++ b/src/main/java/org/opensearch/security/http/ApiTokenAuthenticator.java
@@ -145,7 +145,6 @@ private AuthCredentials extractCredentials0(final SecurityRequest request, final
                 return null;
             }
 
-            // TODO: handle revocation different from deletion?
             if (!apiTokenRepository.isValidToken(subject)) {
                 log.error("Api token is not allowlisted");
                 return null;
diff --git a/src/test/java/org/opensearch/security/action/apitokens/ApiTokenActionTest.java b/src/test/java/org/opensearch/security/action/apitokens/ApiTokenActionTest.java
index e7193710e1..7c52f07ae7 100644
--- a/src/test/java/org/opensearch/security/action/apitokens/ApiTokenActionTest.java
+++ b/src/test/java/org/opensearch/security/action/apitokens/ApiTokenActionTest.java
@@ -17,17 +17,148 @@
 import java.util.List;
 import java.util.Map;
 
+import com.google.common.collect.ImmutableMap;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.opensearch.cluster.ClusterState;
+import org.opensearch.cluster.metadata.Metadata;
+import org.opensearch.cluster.service.ClusterService;
+import org.opensearch.common.settings.Settings;
+import org.opensearch.common.util.concurrent.ThreadContext;
+import org.opensearch.security.configuration.ConfigurationRepository;
+import org.opensearch.security.privileges.PrivilegesEvaluator;
+import org.opensearch.security.securityconf.FlattenedActionGroups;
+import org.opensearch.security.securityconf.impl.CType;
+import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration;
+import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7;
+import org.opensearch.security.securityconf.impl.v7.RoleV7;
+import org.opensearch.threadpool.ThreadPool;
+
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.is;
+import static org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration.fromMap;
 import static org.junit.Assert.assertThrows;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+@RunWith(MockitoJUnitRunner.class)
 public class ApiTokenActionTest {
+    @Mock
+    private ThreadPool threadPool;
+
+    @Mock
+    private PrivilegesEvaluator privilegesEvaluator;
+
+    @Mock
+    private ConfigurationRepository configurationRepository;
+
+    @Mock
+    private ClusterService clusterService;
+    @Mock
+    private ClusterState clusterState;
+
+    @Mock
+    private Metadata metadata;
+
+    private SecurityDynamicConfiguration<ActionGroupsV7> actionGroupsConfig;
+    private SecurityDynamicConfiguration<RoleV7> rolesConfig;
+    private FlattenedActionGroups flattenedActionGroups;
+    private ApiTokenAction apiTokenAction;
+
+    @Before
+    public void setUp() throws JsonProcessingException {
+        // Setup basic action groups
+
+        actionGroupsConfig = SecurityDynamicConfiguration.fromMap(
+            ImmutableMap.of(
+                "read_group",
+                Map.of("allowed_actions", List.of("read", "get", "search")),
+                "write_group",
+                Map.of("allowed_actions", List.of("write", "create", "index"))
+            ),
+            CType.ACTIONGROUPS
+        );
+
+        rolesConfig = fromMap(
+            ImmutableMap.of(
+                "read_group_logs-123",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("logs-123"), "allowed_actions", List.of("read_group"))),
+                    "cluster_permissions",
+                    Arrays.asList("*")
+                ),
+                "read_group_logs-star",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("logs-*"), "allowed_actions", List.of("read_group"))),
+                    "cluster_permissions",
+                    Arrays.asList("*")
+                ),
+                "write_group_logs-star",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("logs-*"), "allowed_actions", List.of("write_group"))),
+                    "cluster_permissions",
+                    Arrays.asList("*")
+                ),
+                "write_group_logs-123",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("logs-123"), "allowed_actions", List.of("write_group"))),
+                    "cluster_permissions",
+                    Arrays.asList("*")
+                ),
+                "more_permissable_write_group_lo-star",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("lo*"), "allowed_actions", List.of("write_group"))),
+                    "cluster_permissions",
+                    Arrays.asList("*")
+                ),
+                "cluster_monitor",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("lo*"), "allowed_actions", List.of("write_group"))),
+                    "cluster_permissions",
+                    Arrays.asList("cluster_monitor")
+                ),
+                "alias_group",
+                ImmutableMap.of(
+                    "index_permissions",
+                    Arrays.asList(ImmutableMap.of("index_patterns", List.of("logs"), "allowed_actions", List.of("read"))),
+                    "cluster_permissions",
+                    Arrays.asList("cluster_monitor")
+                )
+
+            ),
+            CType.ROLES
+        );
 
-    private final ApiTokenAction apiTokenAction = new ApiTokenAction(mock(ApiTokenRepository.class));
+        when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
+
+        apiTokenAction = new ApiTokenAction(
+
+            threadPool,
+            configurationRepository,
+            privilegesEvaluator,
+            Settings.EMPTY,
+            null,
+            null,
+            null,
+            null,
+            null,
+            clusterService,
+            null
+        );
+
+    }
 
     @Test
     public void testCreateIndexPermission() {
diff --git a/src/test/java/org/opensearch/security/action/apitokens/ApiTokenAuthenticatorTest.java b/src/test/java/org/opensearch/security/action/apitokens/ApiTokenAuthenticatorTest.java
index c24c632fa0..b6c5e0b0f1 100644
--- a/src/test/java/org/opensearch/security/action/apitokens/ApiTokenAuthenticatorTest.java
+++ b/src/test/java/org/opensearch/security/action/apitokens/ApiTokenAuthenticatorTest.java
@@ -48,6 +48,7 @@ public class ApiTokenAuthenticatorTest {
     private ApiTokenAuthenticator authenticator;
     @Mock
     private Logger log;
+
     @Mock
     private ApiTokenRepository apiTokenRepository;