Validate API client scopes
Define permissions for API client applications using scopes and validate them in your API server
Scopes define which permissions an API client application can request when accessing your APIs. When you register an API client for your customers, you assign scopes that determine what actions the client app can perform. Scalekit includes these scopes in the access token when the client app authenticates.
This guide shows you how to register API clients with specific scopes for your customers and how to validate those scopes in your API server.
-
Register an API client with scopes
Section titled “Register an API client with scopes”When you enable API client registration for your customers, you define the scopes that their applications can request. For example, you might create an API client for a customer’s deployment service with scopes like
deploy:applicationsandread:deployments.Register an API client with specific scopes // Use case: Your customer requests API access for their deployment automation.// You register an API client app with the appropriate scopes.import { ScalekitClient } from '@scalekit-sdk/node';// Initialize Scalekit client (see installation guide for setup)const scalekit = new ScalekitClient(process.env.SCALEKIT_ENVIRONMENT_URL,process.env.SCALEKIT_CLIENT_ID,process.env.SCALEKIT_CLIENT_SECRET);async function createAPIClient() {try {// Define API client details with scopes your customer's app needsconst clientDetails = {name: 'GitHub Actions Deployment Service',description: 'Service account for GitHub Actions to deploy applications to production',scopes: ['deploy:applications', 'read:deployments'],expiry: 3600, // Token expiry in seconds};// API call to register the clientconst response = await scalekit.m2m.createClient({organizationId: process.env.SCALEKIT_ORGANIZATION_ID,client: clientDetails,});// Response contains client details and the plain_secret (only returned once)const clientId = response.client.client_id;const plainSecret = response.plain_secret;// Provide these credentials to your customer securelyconsole.log('Created API client:', clientId);} catch (error) {console.error('Error creating API client:', error);}}createAPIClient();Register an API client with specific scopes # Use case: Your customer requests API access for their deployment automation.# You register an API client app with the appropriate scopes.import osfrom scalekit import ScalekitClient# Initialize Scalekit client (see installation guide for setup)scalekit_client = ScalekitClient(env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),client_id=os.getenv("SCALEKIT_CLIENT_ID"),client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"))try:# Define API client details with scopes your customer's app needsfrom scalekit.v1.clients.clients_pb2 import OrganizationClientclient_details = OrganizationClient(name="GitHub Actions Deployment Service",description="Service account for GitHub Actions to deploy applications to production",scopes=["deploy:applications", "read:deployments"],expiry=3600 # Token expiry in seconds)# API call to register the clientresponse = scalekit_client.m2m_client.create_organization_client(organization_id=os.getenv("SCALEKIT_ORGANIZATION_ID"),m2m_client=client_details)# Response contains client details and the plain_secret (only returned once)client_id = response.client.client_idplain_secret = response.plain_secret# Provide these credentials to your customer securelyprint("Created API client:", client_id)except Exception as e:print("Error creating API client:", e)Register an API client with specific scopes // Use case: Your customer requests API access for their deployment automation.// You register an API client app with the appropriate scopes.package mainimport ("context""fmt""os""time""github.com/scalekit-inc/scalekit-sdk-go""github.com/scalekit-inc/scalekit-sdk-go/pkg/grpc/organizations")func main() {// Initialize Scalekit client (see installation guide for setup)scalekitClient := scalekit.NewScalekitClient(os.Getenv("SCALEKIT_ENVIRONMENT_URL"),os.Getenv("SCALEKIT_CLIENT_ID"),os.Getenv("SCALEKIT_CLIENT_SECRET"),)ctx := context.Background()// Define API client details with scopes your customer's app needsclientDetails := &organizations.OrganizationClient{Name: "GitHub Actions Deployment Service",Description: "Service account for GitHub Actions to deploy applications to production",Scopes: []string{"deploy:applications", "read:deployments"},Expiry: int64(time.Hour.Seconds()), // Token expiry in seconds}// API call to register the clientresponse, err := scalekitClient.Organization.CreateOrganizationClient(ctx,os.Getenv("SCALEKIT_ORGANIZATION_ID"),clientDetails,)if err != nil {fmt.Println("Error creating API client:", err)return}// Response contains client details and the plain_secret (only returned once)clientID := response.Client.ClientIdplainSecret := response.PlainSecret// Provide these credentials to your customer securelyfmt.Println("Created API client:", clientID)}Register an API client with specific scopes // Use case: Your customer requests API access for their deployment automation.// You register an API client app with the appropriate scopes.import com.scalekit.ScalekitClient;import com.scalekit.grpc.scalekit.v1.organizations.OrganizationClient;import com.scalekit.grpc.scalekit.v1.organizations.CreateOrganizationClientResponse;import java.util.Arrays;public class CreateAPIClient {public static void main(String[] args) {// Initialize Scalekit client (see installation guide for setup)ScalekitClient scalekitClient = new ScalekitClient(System.getenv("SCALEKIT_ENVIRONMENT_URL"),System.getenv("SCALEKIT_CLIENT_ID"),System.getenv("SCALEKIT_CLIENT_SECRET"));try {// Define API client details with scopes your customer's app needsOrganizationClient clientDetails = OrganizationClient.newBuilder().setName("GitHub Actions Deployment Service").setDescription("Service account for GitHub Actions to deploy applications to production").addAllScopes(Arrays.asList("deploy:applications", "read:deployments")).setExpiry(3600L) // Token expiry in seconds.build();// API call to register the clientCreateOrganizationClientResponse response = scalekitClient.organizations().createOrganizationClient(System.getenv("SCALEKIT_ORGANIZATION_ID"),clientDetails);// Response contains client details and the plain_secret (only returned once)String clientId = response.getClient().getClientId();String plainSecret = response.getPlainSecret();// Provide these credentials to your customer securelySystem.out.println("Created API client: " + clientId);} catch (Exception e) {System.err.println("Error creating API client: " + e.getMessage());}}}Register an API client with specific scopes curl -L 'https://<SCALEKIT_ENVIRONMENT_URL>/api/v1/organizations/<ORGANIZATION_ID>/clients' \-H 'Content-Type: application/json' \-H 'Authorization: Bearer <SCALEKIT_ACCESS_TOKEN>' \-d '{"name": "GitHub Actions Deployment Service","description": "Service account for GitHub Actions to deploy applications to production","scopes": ["deploy:applications","read:deployments"],"expiry": 3600}'Sample response {"client": {"client_id": "m2morg_68315758685323697","secrets": [{"id": "sks_68315758802764209","create_time": "2025-04-16T06:56:05.360Z","update_time": "2025-04-16T06:56:05.367190455Z","secret_suffix": "UZ0X","status": "ACTIVE","last_used_time": "2025-04-16T06:56:05.360Z"}],"name": "GitHub Actions Deployment Service","description": "Service account for GitHub Actions to deploy applications to production","organization_id": "org_59615193906282635","create_time": "2025-04-16T06:56:05.290Z","update_time": "2025-04-16T06:56:05.292145150Z","scopes": ["deploy:applications","read:deployments"]},"plain_secret": "test_ly8G57h0ErRJSObJI6dShkoaq6bigo11Dxcfa6reKG1kKNVbqBKW4H5Ctmb5UZ0X"} -
Validate API client scopes
Section titled “Validate API client scopes”When your API server receives a request from an API client app, you must validate the scopes present in the access token provided in the
Authorizationheader. The access token is a JSON Web Token (JWT).First, let’s look at the claims inside a decoded JWT payload. Scalekit encodes the granted scopes in the
scopesfield.Example decoded access token {"client_id": "m2morg_69038819013296423","exp": 1745305340,"iat": 1745218940,"iss": "<SCALEKIT_ENVIRONMENT_URL>","jti": "tkn_69041163914445100","nbf": 1745218940,"oid": "org_59615193906282635","scopes": ["deploy:applications","read:deployments"],"sub": "m2morg_69038819013296423"}Your API server should inspect the
scopesarray in the token payload to authorize the requested operation. Here’s how you validate the token and check for a specific scope in your API server.Example Express.js middleware for scope validation // Security: ALWAYS validate the access token on your server before trusting its claims.// This prevents token forgery and ensures the token has not expired.import { ScalekitClient } from '@scalekit-sdk/node';import jwt from 'jsonwebtoken';import jwksClient from 'jwks-rsa';const scalekit = new ScalekitClient(process.env.SCALEKIT_ENVIRONMENT_URL,process.env.SCALEKIT_CLIENT_ID,process.env.SCALEKIT_CLIENT_SECRET);// Setup JWKS client for token verificationconst client = jwksClient({jwksUri: `${process.env.SCALEKIT_ENVIRONMENT_URL}/.well-known/jwks.json`,cache: true});async function getPublicKey(header) {return new Promise((resolve, reject) => {client.getSigningKey(header.kid, (err, key) => {if (err) reject(err);else resolve(key.getPublicKey());});});}async function checkPermissions(req, res, next) {const authHeader = req.headers.authorization;if (!authHeader || !authHeader.startsWith('Bearer ')) {return res.status(401).send('Unauthorized: Missing token');}const token = authHeader.split(' ')[1];try {// Decode to get the header with kidconst decoded = jwt.decode(token, { complete: true });const publicKey = await getPublicKey(decoded.header);// Verify the token signature and claimsconst verified = jwt.verify(token, publicKey, {algorithms: ['RS256'],complete: true});const decodedToken = verified.payload;// Check if the API client app has the required scopeconst requiredScope = 'deploy:applications';if (decodedToken.scopes && decodedToken.scopes.includes(requiredScope)) {// API client app has the required scope, proceed with the requestnext();} else {// API client app does not have the required scoperes.status(403).send('Forbidden: Insufficient permissions');}} catch (error) {// Token is invalid or expiredres.status(401).send('Unauthorized: Invalid token');}}Example Flask decorator for scope validation # Security: ALWAYS validate the access token on your server before trusting its claims.# This prevents token forgery and ensures the token has not expired.import osimport functoolsfrom scalekit import ScalekitClientfrom flask import request, jsonify# Initialize Scalekit clientscalekit_client = ScalekitClient(env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),client_id=os.getenv("SCALEKIT_CLIENT_ID"),client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"))def check_permissions(required_scope):def decorator(f):@functools.wraps(f)def decorated_function(*args, **kwargs):auth_header = request.headers.get('Authorization')if not auth_header or not auth_header.startswith('Bearer '):return jsonify({"error": "Unauthorized: Missing token"}), 401token = auth_header.split(' ')[1]try:# Validate the token using the Scalekit SDKclaims = scalekit_client.validate_access_token_and_get_claims(token=token)# Check if the API client app has the required scopeif required_scope in claims.get('scopes', []):# API client app has the required scopereturn f(*args, **kwargs)else:# API client app does not have the required scopereturn jsonify({"error": "Forbidden: Insufficient permissions"}), 403except Exception as e:# Token is invalid or expiredreturn jsonify({"error": "Unauthorized: Invalid token"}), 401return decorated_functionreturn decorator# Example usage in a Flask route# @app.route('/deploy', methods=['POST'])# @check_permissions('deploy:applications')# def deploy_application():# return jsonify({"message": "Deployment successful"})Example Go middleware for scope validation // Security: ALWAYS validate the access token on your server before trusting its claims.// This prevents token forgery and ensures the token has not expired.package mainimport ("context""net/http""os""strings""github.com/scalekit-inc/scalekit-sdk-go")func CheckPermissions(next http.Handler, requiredScope string) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {authHeader := r.Header.Get("Authorization")if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {http.Error(w, "Unauthorized: Missing token", http.StatusUnauthorized)return}token := strings.TrimPrefix(authHeader, "Bearer ")// Initialize Scalekit client (should be a singleton in a real app)scalekitClient := scalekit.NewScalekitClient(os.Getenv("SCALEKIT_ENVIRONMENT_URL"),os.Getenv("SCALEKIT_CLIENT_ID"),os.Getenv("SCALEKIT_CLIENT_SECRET"),)// Validate the token using the Scalekit SDKdecodedToken, err := scalekitClient.Token.VerifyToken(context.Background(), token, nil)if err != nil {http.Error(w, "Unauthorized: Invalid token", http.StatusUnauthorized)return}// Check if the API client app has the required scopehasScope := falsefor _, scope := range decodedToken.Claims.Scopes {if scope == requiredScope {hasScope = truebreak}}if hasScope {// API client app has the required scopenext.ServeHTTP(w, r)} else {// API client app does not have the required scopehttp.Error(w, "Forbidden: Insufficient permissions", http.StatusForbidden)}})}Example Spring Boot interceptor for scope validation // Security: ALWAYS validate the access token on your server before trusting its claims.// This prevents token forgery and ensures the token has not expired.import com.scalekit.ScalekitClient;import com.scalekit.internal.http.TokenClaims;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;@Componentpublic class PermissionInterceptor implements HandlerInterceptor {private final ScalekitClient scalekitClient;public PermissionInterceptor(ScalekitClient scalekitClient) {this.scalekitClient = scalekitClient;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String authHeader = request.getHeader("Authorization");if (authHeader == null || !authHeader.startsWith("Bearer ")) {response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Missing token");return false;}String token = authHeader.substring(7);try {// Validate the token using the Scalekit SDKTokenClaims claims = scalekitClient.token().verifyToken(token, null);// Check if the API client app has the required scopeString requiredScope = "deploy:applications";if (claims.getScopes() != null && claims.getScopes().contains(requiredScope)) {// API client app has the required scopereturn true;} else {// API client app does not have the required scoperesponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden: Insufficient permissions");return false;}} catch (Exception e) {// Token is invalid or expiredresponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Invalid token");return false;}}}