AS3W (Access String: Who, What, When) is a lightweight authorization mechanism designed to associate access control rules directly with data stored in a database. Rather than relying on external access-control tables or centralized policy engines, AS3W embeds authorization rules as structured strings attached to individual records.
The objective of AS3W is to provide a compact, deterministic, and easily auditable authorization mechanism that answers three fundamental questions: who may access a resource, what actions are permitted, and until when the authorization remains valid.
Conceptual Overview
In AS3W, authorization is expressed as data. Each record in the database may contain an Access String, which encodes the authorization policy applicable to that record. At runtime, the system generates an Access Request String describing the current request context. The request context describe : « Who » try to access the data, « What » he intend to do with it and « When » he is performing it. The data record is returned only if the request string matches the stored access string using regular-expression evaluation.
Access is granted if the requesting user is explicitly authorized OR if at least one of the user’s groups is authorized.
Access String
An Access String is a backslash-delimited expression composed of optional and mandatory fields:
[RULENAME]\users:#<ID>,#<ID>|groups:#<ID>,#<ID>\actions:#<ACTION>,#<ACTION>\until:<TIMESTAMP>\[COMMENT]
The Access String may include an optional rule name and comment for readability, but these elements are ignored during authorization evaluation.
The users field specifies explicit user identifiers allowed to access the resource. The groups field specifies group identifiers. If both fields are present, authorization succeeds when either the user matches the users field or at least one of the user’s groups matches the groups field.
The actions field defines the set of permitted actions, such as read, write, or delete. The until field defines the expiration timestamp, expressed as epoch time in seconds or milliseconds. If the current time exceeds this value, the rule no longer grants access.
Examples of access Strings
MyRule1\users:@e8f58e5f85e8f58\action:@read\until:176427694
users:@87844545445\groups:@devops\action:@read\until:176899568\just another rule
groups:@devops\action:@read\until:1764271971
Each example expresses a complete authorization policy embedded directly in the data record (e.g in a es3w attribute).
Access Request String
An Access Request String is generated dynamically at runtime and represents the current access context. It encodes the requesting user’s identifier, the groups to which the user belongs, the actions that will be performed on the data and the current timestamp.
Example:
users:@e8f58e5f85e8f58|groups:@devops,admin\action:@read\until:176427694
This request indicates that the user e8f58e5f85e8f58, a member of the devops and admin groups, is attempting to perform a read operation at the specified time.
Authorization Evaluation
The verification process normalizes the stored Access String and a regular expression is then constructed from the Access Request String. Access is granted if and only if the generated regular expression matches the normalized Access String.
This approach ensures deterministic authorization behavior with no side effects. If a required field is missing or does not match, the authorization attempt fails.
Time-Based Authorization
The until field enables time-limited access without requiring background jobs or scheduled cleanup. The timestamp comparison is implemented using a numeric regular expression that only matches values greater than or equal to the current time. Once the expiration time is reached, the rule automatically becomes invalid.
Proof of Concept
function esc(str) {
return String(str)
.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")
.trim();
}
async function _time_regex_filter(ts) {
const digits = String(ts).split('').map(Number);
let regex = '';
for (let i = 0; i < digits.length; i++) {
const n = digits[i];
const L = digits.length - i - 1;
regex += n < 9
? `([${n+1}-9]\\d{${L}}|${n}`
: `(${n}`;
}
return regex + ")))))))))";
}
async function _security_check(users, groups, actions){
forbidden = ["\\","(",")"];
users.forEach((e) => {
if(forbidden.some(c => e.includes(c))) throw new Error("users include a forbidden character");
});
groups.forEach((e) => {
if(forbidden.some(c => e.includes(c))) throw new Error("groups include a forbidden character");
});
actions.forEach((e) => {
if(forbidden.some(c => e.includes(c))) throw new Error("action include a forbidden character");
});
return false;
}
async function AS3W_ACCESS_STRING(users=[], groups=[], actions=[], time=null) {
_security_check(users, groups, actions);
users = users.join(",#");
groups = groups.join(",#");
actions = actions.join(",#");
users = users ? `users:#${users}` : "";
groups = groups ? `groups:#${groups}` : "";
actions = actions ? `action:#${actions}` : "";
time = time ? `until:${esc(time)}` : "";
return [users, groups, actions, time]
.filter(x => x !== "")
.join("\\");
}
/**
* Build the REGEX used to CHECK ACCESS
*/
async function AS3W_ACCESS_REQUEST(users = [], groups = [], actions = [], time = null) {
_security_check(users, groups, actions);
// Sanitize arrays
const escUsers = users.map(u => esc(u));
const escGroups = groups.map(g => esc(g));
const escActions = actions.map(a => esc(a));
forbidden = ["\\","(",")"]
// Build alternation groups: (u1|u2|u3)
const userAlternatives = escUsers.join("|") || "\\*";
const groupAlternatives = escGroups.join("|") || "\\*";
const actionAlternatives = escActions.join("|") || "\\*";
let timeRegex = "";
if (time && time > 0) {
timeRegex = await _time_regex_filter(time);
}
else{
throw new Error("Time should be greater than 0");
}
const regex =
"^" +
"([^\\\\]*\\\\)*" +
"(" +
"users:.*#(" + userAlternatives + "|\\*)(,.*|\\b)" +
"(\\\\groups:.*)*|(users:.*\\\\)*" +
"groups:.*#(" + groupAlternatives + "|\\*)(,.*|\\b)" +
")" +
"\\\\action:.*#(" + actionAlternatives + "|\\*)(,.*|\\b)" +
(time
? "\\\\until:(" + timeRegex + ")"
: "(\\\\until:(" + timeRegex + "))?") +
"\\s*$";
return regex;
}
async function AS3W_VERIFY(accessString, requestString) {
// 1. Normalize stored Access String (remove spaces, trim)
const normalizedAS = String(accessString).trim().replace(/\s+/g, "");
// 3. Build regex object
let requestRegex;
try {
requestRegex = new RegExp(requestString);
} catch (err) {
console.error("Invalid AS3W regex:", requestRegexString);
return false;
}
// 4. Perform match
return requestRegex.test(normalizedAS);
}
// TEST
(async () => {
const access_string = await AS3W_ACCESS_STRING(
[],
["admin", "devops"],
["read","delete"],
176427694
)
console.log(access_string);
const access_request = await AS3W_ACCESS_REQUEST(
["[email protected]"],
["admin"],
["read"],
"176427693"
)
console.log(access_request);
const ok = await AS3W_VERIFY(
access_string,
access_request
);
console.log("ACCESS GRANTED:", ok);
})();
Security Considerations
AS3W has not been tested against vulnerabilities and should not be implemented in production environments.
Strict input constraints should be enforce to ensure safe evaluation. Reserved characters should be forbidden in identifiers and all values used to construct regular expressions are escaped. The regular expressions must be generated by trusted application code and not directly user-controlled. The mechanism follows a fail-closed model: any mismatch results in access denial.
Intended Use Cases
AS3W is well suited for object-level authorization, document sharing systems, event-based platforms, and multi-tenant architectures where permissions must be tightly coupled to individual records. Its simplicity makes it particularly effective in systems where authorization logic must remain transparent, portable, and easy to audit.
