{"record":{"id":"dc7b398fe4eb3a71","repo":"floci-io/floci","slug":"unauthorized","errorCode":"Unauthorized","errorMessage":"Not Authorized","messagePattern":"Not Authorized","errorType":"error_code","errorClass":"VtlErrorSignal","httpStatus":null,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/appsync/graphql/util/AppSyncUtil.java","lineNumber":248,"sourceCode":"    }\n\n    public void appendError(String message, String errorType, Object data, Object errorInfo) {\n        appendErrorInternal(message, errorType, data, errorInfo);\n    }\n\n    private void appendErrorInternal(String message, String errorType, Object data, Object errorInfo) {\n        if (errorList != null) {\n            Map<String, Object> error = new LinkedHashMap<>();\n            error.put(\"message\", message);\n            error.put(\"type\", errorType);\n            if (data != null) error.put(\"data\", data);\n            if (errorInfo != null) error.put(\"errorInfo\", errorInfo);\n            errorList.add(error);\n        }\n    }\n\n    public void unauthorized() {\n        throw new VtlErrorSignal(\"Not Authorized\", \"Unauthorized\", null, null);\n    }\n\n    public void validate(boolean condition, String message) {\n        validate(condition, message, \"CustomTemplateException\");\n    }\n\n    public void validate(boolean condition, String message, String errorType) {\n        validate(condition, message, errorType, null);\n    }\n\n    public void validate(boolean condition, String message, String errorType, Object data) {\n        if (!condition) {\n            throw new VtlErrorSignal(message, errorType, data, null);\n        }\n    }\n}\n","sourceCodeStart":230,"sourceCodeEnd":265,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/appsync/graphql/util/AppSyncUtil.java#L230-L265","documentation":"Implements $util.unauthorized(): the VTL equivalent of rejecting a request for missing/failed authorization. The resolver aborts with message 'Not Authorized' and errorType 'Unauthorized', mirroring AppSync's behavior when authorization checks in a mapping template fail.","triggerScenarios":"A request/response mapping template calls $util.unauthorized() — e.g. #if($ctx.identity == null) or a group-membership check like #if(!$ctx.identity.claims.get(\"cognito:groups\").contains(\"admins\")) — and the guard trips. Common in field-level authorization on top of API-level auth.","commonSituations":"Cognito/API-key authorizers where the test client sends no or wrong credentials; AppSync API configured with IAM auth but the request signed with a different mechanism; role/group claim names that don't match what the template checks after migrating identity providers.","solutions":["Send the correct authorization for the API's auth mode: signed requests (SigV4 for IAM), Authorization header (Cognito JWT), or x-api-key header.","Verify the identity claim the template checks actually exists, e.g. decode the JWT and inspect $ctx.identity.claims.","Relax or fix the template guard if it rejects legitimate users (wrong group name, null-check inverted).","In the emulator, ensure the AppSync API's default authentication mode matches what the client uses."],"exampleFix":"## before\n#if(!$ctx.identity.claims.get(\"cognito:groups\").contains(\"admins\"))\n  $util.unauthorized()\n#end\n\n## after\n#set($groups = $util.defaultIfNull($ctx.identity.claims.get(\"cognito:groups\"), []))\n#if(!$groups.contains(\"admins\"))\n  $util.unauthorized()\n#end","handlingStrategy":"validation","validationCode":"// Client: prove credentials are present before the GraphQL call\nif (authMode == IAM && signingCredentials == null) throw new IllegalStateException(\"IAM credentials required\");\nif (authMode == API_KEY && apiKey == null) throw new IllegalStateException(\"API key required\");\nif (authMode == USER_POOLS && jwt == null) throw new IllegalStateException(\"Cognito token required\");","typeGuard":null,"tryCatchPattern":"if (response.hasErrors() && response.getErrors().stream()\n        .anyMatch(er -> \"Unauthorized\".equals(er.getErrorType()) || \"Not Authorized\".equals(er.getMessage()))) {\n    throw new UnauthorizedException(\"refresh credentials and retry\");\n}","preventionTips":["Refresh tokens/signing credentials before they expire; treat Unauthorized as non-retryable until re-auth.","Match the client auth mechanism to the API's default authorization mode.","Null-guard $ctx.identity and its claims inside templates before dereferencing."],"tags":["appsync","vtl","authorization","graphql","unauthorized"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}