[{"data":1,"prerenderedAt":1811},["ShallowReactive",2],{"navigation":3,"/essentials/identity":287,"/essentials/identity-surround":1806},[4,24,64,112,148,180,263,267,271,275,279,283],{"title":5,"path":6,"stem":7,"children":8},"Getting Started","/getting-started","1.getting-started",[9,12,16,20],{"title":10,"path":6,"stem":11},"Introduction","1.getting-started/index",{"title":13,"path":14,"stem":15},"Installation","/getting-started/installation","1.getting-started/1.installation",{"title":17,"path":18,"stem":19},"Quickstart","/getting-started/quickstart","1.getting-started/2.quickstart",{"title":21,"path":22,"stem":23},"Build Your First API","/getting-started/first-api","1.getting-started/3.first-api",{"title":25,"path":26,"stem":27,"children":28},"Essentials","/essentials","2.essentials",[29,32,36,40,44,48,52,56,60],{"title":30,"path":26,"stem":31},"Core concepts","2.essentials/index",{"title":33,"path":34,"stem":35},"Routing","/essentials/routing","2.essentials/1.routing",{"title":37,"path":38,"stem":39},"Authentication","/essentials/authentication","2.essentials/2.authentication",{"title":41,"path":42,"stem":43},"Identity & User Store","/essentials/identity","2.essentials/3.identity",{"title":45,"path":46,"stem":47},"Requests & Responses","/essentials/requests-responses","2.essentials/4.requests-responses",{"title":49,"path":50,"stem":51},"Controllers","/essentials/controllers","2.essentials/5.controllers",{"title":53,"path":54,"stem":55},"Database","/essentials/database","2.essentials/6.database",{"title":57,"path":58,"stem":59},"Validation","/essentials/validation","2.essentials/7.validation",{"title":61,"path":62,"stem":63},"Migrations","/essentials/migrations","2.essentials/8.migrations",{"title":65,"path":66,"stem":67,"children":68},"Features","/features","3.features",[69,72,76,80,84,88,92,96,100,104,108],{"title":70,"path":66,"stem":71},"Built‑in Capabilities","3.features/index",{"title":73,"path":74,"stem":75},"Caching","/features/caching","3.features/caching",{"title":77,"path":78,"stem":79},"CORS & CSRF","/features/cors-csrf","3.features/cors-csrf",{"title":81,"path":82,"stem":83},"Distributed Locks","/features/distributed-locks","3.features/distributed-locks",{"title":85,"path":86,"stem":87},"Events & Listeners","/features/events","3.features/events",{"title":89,"path":90,"stem":91},"Field Selection","/features/field-selection","3.features/field-selection",{"title":93,"path":94,"stem":95},"File Uploads","/features/file-uploads","3.features/file-uploads",{"title":97,"path":98,"stem":99},"Notifications","/features/notifications","3.features/notifications",{"title":101,"path":102,"stem":103},"Queues & Jobs","/features/queues-jobs","3.features/queues-jobs",{"title":105,"path":106,"stem":107},"Rate Limiting","/features/rate-limiting","3.features/rate-limiting",{"title":109,"path":110,"stem":111},"Task Scheduling","/features/scheduling","3.features/scheduling",{"title":113,"path":114,"stem":115,"children":116},"Advanced","/advanced","4.advanced",[117,120,124,128,132,136,140,144],{"title":118,"path":114,"stem":119},"Techniques and Patterns","4.advanced/index",{"title":121,"path":122,"stem":123},"Configuration","/advanced/configuration","4.advanced/configuration",{"title":125,"path":126,"stem":127},"Dependency Injection","/advanced/dependency-injection","4.advanced/dependency-injection",{"title":129,"path":130,"stem":131},"Middleware","/advanced/middleware","4.advanced/middleware",{"title":133,"path":134,"stem":135},"Performance","/advanced/performance","4.advanced/performance",{"title":137,"path":138,"stem":139},"Repositories","/advanced/repositories","4.advanced/repositories",{"title":141,"path":142,"stem":143},"Service Providers","/advanced/service-providers","4.advanced/service-providers",{"title":145,"path":146,"stem":147},"Testing","/advanced/testing","4.advanced/testing",{"title":149,"path":150,"stem":151,"children":152},"Deployment","/deployment","5.deployment",[153,156,160,164,168,172,176],{"title":154,"path":150,"stem":155},"Options for Deployment","5.deployment/index",{"title":157,"path":158,"stem":159},"Docker Deployment","/deployment/docker","5.deployment/docker",{"title":161,"path":162,"stem":163},"Logging","/deployment/logging","5.deployment/logging",{"title":165,"path":166,"stem":167},"Monitoring","/deployment/monitoring","5.deployment/monitoring",{"title":169,"path":170,"stem":171},"Production Setup","/deployment/production","5.deployment/production",{"title":173,"path":174,"stem":175},"Security Hardening","/deployment/security-hardening","5.deployment/security-hardening",{"title":177,"path":178,"stem":179},"Zero Downtime Deployment","/deployment/zero-downtime","5.deployment/zero-downtime",{"title":181,"path":182,"stem":183,"children":184},"Cookbook","/cookbook","6.cookbook",[185,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,259],{"title":186,"path":182,"stem":187},"Recipes & How‑Tos","6.cookbook/index",{"title":189,"path":190,"stem":191},"Routing Recipes","/cookbook/routing","6.cookbook/1.routing",{"title":193,"path":194,"stem":195},"Caching Recipes","/cookbook/caching","6.cookbook/10.caching",{"title":197,"path":198,"stem":199},"Queue Infrastructure","/cookbook/queues-and-jobs","6.cookbook/11.queues-and-jobs",{"title":201,"path":202,"stem":203},"Notification Channels & Templates","/cookbook/notifications","6.cookbook/13.notifications",{"title":205,"path":206,"stem":207},"Storage","/cookbook/storage","6.cookbook/15.storage",{"title":209,"path":210,"stem":211},"Image Processing Examples","/cookbook/image-processing","6.cookbook/16.image-processing",{"title":213,"path":214,"stem":215},"Permissions and Authorization","/cookbook/permissions-and-authorization","6.cookbook/18.permissions-and-authorization",{"title":217,"path":218,"stem":219},"Session Analytics","/cookbook/sessions-analytics","6.cookbook/19.sessions-analytics",{"title":221,"path":222,"stem":223},"Writing Middleware","/cookbook/middleware","6.cookbook/2.middleware",{"title":225,"path":226,"stem":227},"API Metrics","/cookbook/api-metrics","6.cookbook/20.api-metrics",{"title":229,"path":230,"stem":231},"Performance Deep Dive","/cookbook/performance","6.cookbook/21.performance",{"title":233,"path":234,"stem":235},"Memory Management","/cookbook/memory-management","6.cookbook/22.memory-management",{"title":237,"path":238,"stem":239},"Writing Console Commands","/cookbook/console-commands","6.cookbook/23.console-commands",{"title":241,"path":242,"stem":243},"Glueful Extensions","/cookbook/extensions","6.cookbook/25.extensions",{"title":245,"path":246,"stem":247},"Error Handling Guide","/cookbook/error-handling","6.cookbook/4.error-handling",{"title":249,"path":250,"stem":251},"Security Guide","/cookbook/security","6.cookbook/5.security",{"title":253,"path":254,"stem":255},"Service Options Resolver","/cookbook/configuration","6.cookbook/6.configuration",{"title":161,"path":257,"stem":258},"/cookbook/logging","6.cookbook/7.logging",{"title":260,"path":261,"stem":262},"Database Advanced Features","/cookbook/database","6.cookbook/8.database",{"title":264,"path":265,"stem":266},"API Reference","/api-reference","7.api-reference",{"title":268,"path":269,"stem":270},"CLI Reference","/cli-reference","8.cli-reference",{"title":272,"path":273,"stem":274},"Contributing","/contributing","9.contributing",{"title":276,"path":277,"stem":278},"Extensions","/extensions","extensions",{"title":280,"path":281,"stem":282},"Release Notes","/releases","releases",{"title":284,"path":285,"stem":286},"Release Archive","/releases-archive","releases-archive",{"id":288,"title":41,"body":289,"description":1800,"extension":1801,"links":1802,"meta":1803,"navigation":556,"path":42,"seo":1804,"stem":43,"__hash__":1805},"docs/2.essentials/3.identity.md",{"type":290,"value":291,"toc":1787},"minimark",[292,325,368,373,462,466,476,488,491,510,724,738,742,751,770,829,844,849,855,962,979,983,991,1121,1278,1290,1294,1309,1554,1664,1674,1678,1687,1739,1743,1761,1765,1783],[293,294,295,296,300,301,305,306,310,311,314,315,324],"p",{},"Glueful's core is ",[297,298,299],"strong",{},"provider-agnostic about who your users are."," The framework owns the security spine — sessions, tokens, API keys, auth middleware, authorization — but the ",[302,303,304],"em",{},"concrete user store"," (the ",[307,308,309],"code",{},"users"," table, password hashing, account lifecycle) lives in an ",[297,312,313],{},"extension"," behind a small contract. The first-party store is ",[316,317,321],"a",{"href":318,"rel":319},"https://github.com/glueful/users",[320],"nofollow",[307,322,323],{},"glueful/users","; you can swap your own (LDAP, an existing database, a SaaS directory) by implementing one interface.",[326,327,328],"blockquote",{},[293,329,330,333,334,337,338,341,342,345,346,348,349,352,353,337,356,359,360,363,364,367],{},[297,331,332],{},"Core ships no user store."," Without one enabled, core binds a fail-closed ",[307,335,336],{},"NullUserProvider"," and ",[297,339,340],{},"authentication is disabled by design"," — every credential check returns ",[307,343,344],{},"null",". A fresh app must enable a store; the api-skeleton enables ",[307,347,323],{}," by default. (The old in-core ",[307,350,351],{},"User","/",[307,354,355],{},"UserRepository",[307,357,358],{},"AuthenticatedUser"," were removed in 1.50 — everything now flows through ",[307,361,362],{},"UserIdentity"," + ",[307,365,366],{},"UserProviderInterface",".)",[369,370,372],"h2",{"id":371},"the-pieces","The pieces",[374,375,376,389],"table",{},[377,378,379],"thead",{},[380,381,382,386],"tr",{},[383,384,385],"th",{},"Type",[383,387,388],{},"Role",[390,391,392,407,417,429,439,452],"tbody",{},[380,393,394,400],{},[395,396,397],"td",{},[307,398,399],{},"Glueful\\Auth\\UserIdentity",[395,401,402,403,406],{},"The one canonical runtime identity — identity facts + claims. Immutable, ",[307,404,405],{},"final",".",[380,408,409,414],{},[395,410,411],{},[307,412,413],{},"Glueful\\Auth\\Contracts\\UserProviderInterface",[395,415,416],{},"Looks users up and verifies credentials. Implemented by a user store.",[380,418,419,424],{},[395,420,421],{},[307,422,423],{},"Glueful\\Auth\\NullUserProvider",[395,425,426,427,406],{},"Fail-closed default binding — every lookup returns ",[307,428,344],{},[380,430,431,436],{},[395,432,433],{},[307,434,435],{},"Glueful\\Auth\\IdentityResolver",[395,437,438],{},"Post-auth: applies the account-status gate and folds in claims providers.",[380,440,441,446],{},[395,442,443],{},[307,444,445],{},"Glueful\\Auth\\Contracts\\IdentityClaimsProviderInterface",[395,447,448,449,406],{},"Decorates an identity with claims (e.g. roles). Implemented by RBAC such as ",[307,450,451],{},"glueful/aegis",[380,453,454,459],{},[395,455,456],{},[307,457,458],{},"Glueful\\Auth\\Contracts\\TwoFactorServiceInterface",[395,460,461],{},"Optional 2FA, provided by an extension.",[369,463,465],{"id":464},"the-login-flow","The login flow",[467,468,473],"pre",{"className":469,"code":471,"language":472},[470],"language-text","POST /auth/login\n  → AuthenticationService::verifyCredentials()\n      → UserProviderInterface::verifyCredentials($identifier, $password)   // the store checks the hash\n      → IdentityResolver::resolve($identity)\n            → status gate (allowed_login_statuses)\n            → fold each IdentityClaimsProvider::enrich()  (roles / permissions / …)\n  → session/token layer attaches sessionUuid + provider, persists identity + claims\n","text",[307,474,471],{"__ignoreMap":475},"",[293,477,478,479,482,483,305,485,487],{},"If no user store is installed, ",[307,480,481],{},"verifyCredentials()"," returns ",[307,484,344],{},[307,486,336],{},") and login fails closed.",[369,489,362],{"id":490},"useridentity",[293,492,493,494,497,498,501,502,505,506,509],{},"The authenticated identity ",[297,495,496],{},"plus its runtime claims"," — not a database row. It carries identity facts (uuid, email, username, status), runtime context (session uuid, provider), and an open ",[297,499,500],{},"claims bag"," (roles, scopes, permissions, …). It's immutable; ",[307,503,504],{},"with*()"," methods return copies. Accessors are ",[297,507,508],{},"methods",":",[467,511,515],{"className":512,"code":513,"language":514,"meta":475,"style":475},"language-php shiki shiki-themes material-theme-lighter github-light github-dark monokai","$user = $this->currentUser;       // ?Glueful\\Auth\\UserIdentity  (or $requestUserContext->getUser())\n\n$user->uuid();                    // 'u-abc123'\n$user->email();                   // ?string\n$user->status();                  // ?string  ('active', …)\n$user->roles();                   // list\u003Cstring> — typed claim accessor\n$user->scopes();                  // list\u003Cstring>\n$user->claim('permissions', []);  // arbitrary claim with a default\n$user->attr('tenant_id');         // non-claim attribute\n$user->toArray();                 // array shape used for session/user_data\n","php",[307,516,517,551,558,578,595,612,629,646,680,707],{"__ignoreMap":475},[518,519,522,526,530,534,538,541,544,547],"span",{"class":520,"line":521},"line",1,[518,523,525],{"class":524},"swvn1","$",[518,527,529],{"class":528},"ss--_","user ",[518,531,533],{"class":532},"sGXK2","=",[518,535,537],{"class":536},"sSBr1"," $this",[518,539,540],{"class":532},"->",[518,542,543],{"class":528},"currentUser",[518,545,546],{"class":524},";",[518,548,550],{"class":549},"ss7Ak","       // ?Glueful\\Auth\\UserIdentity  (or $requestUserContext->getUser())\n",[518,552,554],{"class":520,"line":553},2,[518,555,557],{"emptyLinePlaceholder":556},true,"\n",[518,559,561,563,566,568,572,575],{"class":520,"line":560},3,[518,562,525],{"class":524},[518,564,565],{"class":528},"user",[518,567,540],{"class":532},[518,569,571],{"class":570},"sD0ED","uuid",[518,573,574],{"class":524},"();",[518,576,577],{"class":549},"                    // 'u-abc123'\n",[518,579,581,583,585,587,590,592],{"class":520,"line":580},4,[518,582,525],{"class":524},[518,584,565],{"class":528},[518,586,540],{"class":532},[518,588,589],{"class":570},"email",[518,591,574],{"class":524},[518,593,594],{"class":549},"                   // ?string\n",[518,596,598,600,602,604,607,609],{"class":520,"line":597},5,[518,599,525],{"class":524},[518,601,565],{"class":528},[518,603,540],{"class":532},[518,605,606],{"class":570},"status",[518,608,574],{"class":524},[518,610,611],{"class":549},"                  // ?string  ('active', …)\n",[518,613,615,617,619,621,624,626],{"class":520,"line":614},6,[518,616,525],{"class":524},[518,618,565],{"class":528},[518,620,540],{"class":532},[518,622,623],{"class":570},"roles",[518,625,574],{"class":524},[518,627,628],{"class":549},"                   // list\u003Cstring> — typed claim accessor\n",[518,630,632,634,636,638,641,643],{"class":520,"line":631},7,[518,633,525],{"class":524},[518,635,565],{"class":528},[518,637,540],{"class":532},[518,639,640],{"class":570},"scopes",[518,642,574],{"class":524},[518,644,645],{"class":549},"                  // list\u003Cstring>\n",[518,647,649,651,653,655,658,661,665,669,671,674,677],{"class":520,"line":648},8,[518,650,525],{"class":524},[518,652,565],{"class":528},[518,654,540],{"class":532},[518,656,657],{"class":570},"claim",[518,659,660],{"class":524},"(",[518,662,664],{"class":663},"siCPE","'",[518,666,668],{"class":667},"sLACW","permissions",[518,670,664],{"class":663},[518,672,673],{"class":524},",",[518,675,676],{"class":524}," []);",[518,678,679],{"class":549},"  // arbitrary claim with a default\n",[518,681,683,685,687,689,692,694,696,699,701,704],{"class":520,"line":682},9,[518,684,525],{"class":524},[518,686,565],{"class":528},[518,688,540],{"class":532},[518,690,691],{"class":570},"attr",[518,693,660],{"class":524},[518,695,664],{"class":663},[518,697,698],{"class":667},"tenant_id",[518,700,664],{"class":663},[518,702,703],{"class":524},");",[518,705,706],{"class":549},"         // non-claim attribute\n",[518,708,710,712,714,716,719,721],{"class":520,"line":709},10,[518,711,525],{"class":524},[518,713,565],{"class":528},[518,715,540],{"class":532},[518,717,718],{"class":570},"toArray",[518,720,574],{"class":524},[518,722,723],{"class":549},"                 // array shape used for session/user_data\n",[293,725,726,729,730,733,734,737],{},[297,727,728],{},"Identity facts vs. claims:"," a claims provider can change ",[302,731,732],{},"what a user can do"," (claims), never ",[302,735,736],{},"who they are"," (identity facts) — the resolver re-pins the facts after enrichment.",[369,739,741],{"id":740},"enabling-a-user-store","Enabling a user store",[293,743,744,745,352,747,750],{},"Install and enable the first-party store, then migrate (",[307,746,309],{},[307,748,749],{},"profiles"," ship with the extension; the auth spine ships with core):",[467,752,756],{"className":753,"code":754,"language":755,"meta":475,"style":475},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","composer require glueful/users\n","bash",[307,757,758],{"__ignoreMap":475},[518,759,760,764,767],{"class":520,"line":521},[518,761,763],{"class":762},"sR7ES","composer",[518,765,766],{"class":667}," require",[518,768,769],{"class":667}," glueful/users\n",[467,771,773],{"className":512,"code":772,"language":514,"meta":475,"style":475},"// config/extensions.php\n'enabled' => [\n    'Glueful\\\\Extensions\\\\Users\\\\UsersServiceProvider',\n],\n",[307,774,775,780,795,824],{"__ignoreMap":475},[518,776,777],{"class":520,"line":521},[518,778,779],{"class":549},"// config/extensions.php\n",[518,781,782,784,787,789,792],{"class":520,"line":553},[518,783,664],{"class":663},[518,785,786],{"class":667},"enabled",[518,788,664],{"class":663},[518,790,791],{"class":532}," =>",[518,793,794],{"class":524}," [\n",[518,796,797,800,803,807,809,811,814,816,819,821],{"class":520,"line":560},[518,798,799],{"class":663},"    '",[518,801,802],{"class":667},"Glueful",[518,804,806],{"class":805},"sQeA1","\\\\",[518,808,276],{"class":667},[518,810,806],{"class":805},[518,812,813],{"class":667},"Users",[518,815,806],{"class":805},[518,817,818],{"class":667},"UsersServiceProvider",[518,820,664],{"class":663},[518,822,823],{"class":524},",\n",[518,825,826],{"class":520,"line":580},[518,827,828],{"class":524},"],\n",[467,830,832],{"className":753,"code":831,"language":755,"meta":475,"style":475},"php glueful migrate:run\n",[307,833,834],{"__ignoreMap":475},[518,835,836,838,841],{"class":520,"line":521},[518,837,514],{"class":762},[518,839,840],{"class":667}," glueful",[518,842,843],{"class":667}," migrate:run\n",[845,846,848],"h3",{"id":847},"account-endpoints","Account endpoints",[293,850,851,852,854],{},"With ",[307,853,323],{}," enabled you also get the account lifecycle and read endpoints it ships:",[374,856,857,867],{},[377,858,859],{},[380,860,861,864],{},[383,862,863],{},"Endpoint",[383,865,866],{},"Purpose",[390,868,869,879,896,916,933,946],{},[380,870,871,876],{},[395,872,873],{},[307,874,875],{},"GET /me",[395,877,878],{},"The current authenticated user.",[380,880,881,886],{},[395,882,883],{},[307,884,885],{},"GET /users/{uuid}",[395,887,888,889,892,893,406],{},"Look up a user by uuid. ",[297,890,891],{},"Opt-in"," — ",[307,894,895],{},"USERS_USER_LOOKUP_ENABLED",[380,897,898,903],{},[395,899,900],{},[307,901,902],{},"GET /users",[395,904,905,906,892,908,911,912,915],{},"Paginated user list. ",[297,907,891],{},[307,909,910],{},"USERS_USER_LIST_ENABLED"," (email filtering via ",[307,913,914],{},"USERS_USER_LIST_ALLOW_EMAIL_FILTER",").",[380,917,918,930],{},[395,919,920,923,924,923,927],{},[307,921,922],{},"POST /auth/verify-email",", ",[307,925,926],{},"/auth/verify-otp",[307,928,929],{},"/auth/resend-otp",[395,931,932],{},"Email / OTP verification.",[380,934,935,943],{},[395,936,937,923,940],{},[307,938,939],{},"POST /auth/forgot-password",[307,941,942],{},"/auth/reset-password",[395,944,945],{},"Password recovery.",[380,947,948,959],{},[395,949,950,923,953,923,956],{},[307,951,952],{},"POST /2fa/enable",[307,954,955],{},"/2fa/verify",[307,957,958],{},"/2fa/disable",[395,960,961],{},"Email-PIN two-factor.",[293,963,964,965,968,969,972,973,976,977,915],{},"The lookup and list endpoints are ",[297,966,967],{},"off by default"," and require the ",[307,970,971],{},"users.read"," permission — enable them with the flags above and grant the permission (e.g. ",[307,974,975],{},"php glueful aegis:bootstrap-admin --user=\u003Cuuid>"," if you use ",[307,978,451],{},[369,980,982],{"id":981},"writing-your-own-user-store","Writing your own user store",[293,984,985,986,988,989,509],{},"Implement ",[307,987,366],{}," — three methods, authentication only (registration/profile writes belong to the store) — and alias it to the contract so it replaces ",[307,990,336],{},[467,992,994],{"className":512,"code":993,"language":514,"meta":475,"style":475},"interface UserProviderInterface\n{\n    public function findByUuid(string $uuid): ?UserIdentity;\n    public function findByLogin(string $identifier): ?UserIdentity;            // email/username/etc.\n    public function verifyCredentials(string $identifier, string $password): ?UserIdentity;\n}\n",[307,995,996,1006,1011,1048,1079,1116],{"__ignoreMap":475},[518,997,998,1002],{"class":520,"line":521},[518,999,1001],{"class":1000},"srJo8","interface",[518,1003,1005],{"class":1004},"sKvfc"," UserProviderInterface\n",[518,1007,1008],{"class":520,"line":553},[518,1009,1010],{"class":524},"{\n",[518,1012,1013,1017,1020,1023,1025,1029,1032,1034,1037,1039,1042,1045],{"class":520,"line":560},[518,1014,1016],{"class":1015},"sTNss","    public",[518,1018,1019],{"class":1000}," function",[518,1021,1022],{"class":570}," findByUuid",[518,1024,660],{"class":524},[518,1026,1028],{"class":1027},"shWJe","string",[518,1030,1031],{"class":524}," $",[518,1033,571],{"class":528},[518,1035,1036],{"class":524},")",[518,1038,509],{"class":532},[518,1040,1041],{"class":532}," ?",[518,1043,362],{"class":1044},"s_MOj",[518,1046,1047],{"class":524},";\n",[518,1049,1050,1052,1054,1057,1059,1061,1063,1066,1068,1070,1072,1074,1076],{"class":520,"line":580},[518,1051,1016],{"class":1015},[518,1053,1019],{"class":1000},[518,1055,1056],{"class":570}," findByLogin",[518,1058,660],{"class":524},[518,1060,1028],{"class":1027},[518,1062,1031],{"class":524},[518,1064,1065],{"class":528},"identifier",[518,1067,1036],{"class":524},[518,1069,509],{"class":532},[518,1071,1041],{"class":532},[518,1073,362],{"class":1044},[518,1075,546],{"class":524},[518,1077,1078],{"class":549},"            // email/username/etc.\n",[518,1080,1081,1083,1085,1088,1090,1092,1094,1096,1098,1101,1103,1106,1108,1110,1112,1114],{"class":520,"line":597},[518,1082,1016],{"class":1015},[518,1084,1019],{"class":1000},[518,1086,1087],{"class":570}," verifyCredentials",[518,1089,660],{"class":524},[518,1091,1028],{"class":1027},[518,1093,1031],{"class":524},[518,1095,1065],{"class":528},[518,1097,673],{"class":524},[518,1099,1100],{"class":1027}," string",[518,1102,1031],{"class":524},[518,1104,1105],{"class":528},"password",[518,1107,1036],{"class":524},[518,1109,509],{"class":532},[518,1111,1041],{"class":532},[518,1113,362],{"class":1044},[518,1115,1047],{"class":524},[518,1117,1118],{"class":520,"line":614},[518,1119,1120],{"class":524},"}\n",[467,1122,1124],{"className":512,"code":1123,"language":514,"meta":475,"style":475},"// in your extension's services()\nuse Glueful\\Auth\\Contracts\\UserProviderInterface;\n\nMyUserProvider::class => [\n    'class'     => MyUserProvider::class,\n    'shared'    => true,\n    'arguments' => ['@' . MyDirectoryClient::class],\n    'alias'     => [UserProviderInterface::class], // rebinds the contract away from NullUserProvider\n],\n",[307,1125,1126,1131,1159,1163,1178,1198,1216,1249,1274],{"__ignoreMap":475},[518,1127,1128],{"class":520,"line":521},[518,1129,1130],{"class":549},"// in your extension's services()\n",[518,1132,1133,1136,1140,1144,1147,1149,1152,1154,1157],{"class":520,"line":553},[518,1134,1135],{"class":1027},"use",[518,1137,1139],{"class":1138},"s91G_"," Glueful",[518,1141,1143],{"class":1142},"sv8o3","\\",[518,1145,1146],{"class":1138},"Auth",[518,1148,1143],{"class":1142},[518,1150,1151],{"class":1138},"Contracts",[518,1153,1143],{"class":1142},[518,1155,366],{"class":1156},"seZir",[518,1158,1047],{"class":524},[518,1160,1161],{"class":520,"line":560},[518,1162,557],{"emptyLinePlaceholder":556},[518,1164,1165,1168,1171,1174,1176],{"class":520,"line":580},[518,1166,1167],{"class":1044},"MyUserProvider",[518,1169,1170],{"class":532},"::",[518,1172,1173],{"class":1027},"class",[518,1175,791],{"class":532},[518,1177,794],{"class":524},[518,1179,1180,1182,1184,1186,1189,1192,1194,1196],{"class":520,"line":597},[518,1181,799],{"class":663},[518,1183,1173],{"class":667},[518,1185,664],{"class":663},[518,1187,1188],{"class":532},"     =>",[518,1190,1191],{"class":1044}," MyUserProvider",[518,1193,1170],{"class":532},[518,1195,1173],{"class":1027},[518,1197,823],{"class":524},[518,1199,1200,1202,1205,1207,1210,1214],{"class":520,"line":614},[518,1201,799],{"class":663},[518,1203,1204],{"class":667},"shared",[518,1206,664],{"class":663},[518,1208,1209],{"class":532},"    =>",[518,1211,1213],{"class":1212},"sMTiH"," true",[518,1215,823],{"class":524},[518,1217,1218,1220,1223,1225,1227,1230,1232,1235,1237,1240,1243,1245,1247],{"class":520,"line":631},[518,1219,799],{"class":663},[518,1221,1222],{"class":667},"arguments",[518,1224,664],{"class":663},[518,1226,791],{"class":532},[518,1228,1229],{"class":524}," [",[518,1231,664],{"class":663},[518,1233,1234],{"class":667},"@",[518,1236,664],{"class":663},[518,1238,1239],{"class":532}," .",[518,1241,1242],{"class":1044}," MyDirectoryClient",[518,1244,1170],{"class":532},[518,1246,1173],{"class":1027},[518,1248,828],{"class":524},[518,1250,1251,1253,1256,1258,1260,1262,1264,1266,1268,1271],{"class":520,"line":648},[518,1252,799],{"class":663},[518,1254,1255],{"class":667},"alias",[518,1257,664],{"class":663},[518,1259,1188],{"class":532},[518,1261,1229],{"class":524},[518,1263,366],{"class":1044},[518,1265,1170],{"class":532},[518,1267,1173],{"class":1027},[518,1269,1270],{"class":524},"],",[518,1272,1273],{"class":549}," // rebinds the contract away from NullUserProvider\n",[518,1275,1276],{"class":520,"line":682},[518,1277,828],{"class":524},[293,1279,1280,1281,1283,1284,1286,1287,1289],{},"Return ",[307,1282,362],{}," instances from lookups; for ",[307,1285,481],{}," return the identity on a correct password and ",[307,1288,344],{}," otherwise. Treat the uuid as an opaque principal id.",[369,1291,1293],{"id":1292},"adding-claims-roles-permissions","Adding claims (roles, permissions, …)",[293,1295,1296,1297,1300,1301,1304,1305,1308],{},"A claims provider enriches every authenticated identity after login. Implement ",[307,1298,1299],{},"IdentityClaimsProviderInterface"," and tag it ",[307,1302,1303],{},"identity.claims_provider"," — the ",[307,1306,1307],{},"IdentityResolver"," collects and invokes all of them, additively:",[467,1310,1312],{"className":512,"code":1311,"language":514,"meta":475,"style":475},"use Glueful\\Auth\\Contracts\\IdentityClaimsProviderInterface;\nuse Glueful\\Auth\\UserIdentity;\n\nfinal class MyRoleClaims implements IdentityClaimsProviderInterface\n{\n    public function enrich(UserIdentity $identity): UserIdentity\n    {\n        $roles = $this->roleStore->rolesFor($identity->uuid());   // list\u003Cstring>\n        return $roles === []\n            ? $identity                                           // never fabricate membership\n            : $identity->withClaims([\n                'roles' => array_values(array_unique([...$identity->roles(), ...$roles])),\n            ]);\n    }\n}\n",[307,1313,1314,1334,1350,1354,1371,1375,1400,1405,1442,1458,1471,1489,1537,1543,1549],{"__ignoreMap":475},[518,1315,1316,1318,1320,1322,1324,1326,1328,1330,1332],{"class":520,"line":521},[518,1317,1135],{"class":1027},[518,1319,1139],{"class":1138},[518,1321,1143],{"class":1142},[518,1323,1146],{"class":1138},[518,1325,1143],{"class":1142},[518,1327,1151],{"class":1138},[518,1329,1143],{"class":1142},[518,1331,1299],{"class":1156},[518,1333,1047],{"class":524},[518,1335,1336,1338,1340,1342,1344,1346,1348],{"class":520,"line":553},[518,1337,1135],{"class":1027},[518,1339,1139],{"class":1138},[518,1341,1143],{"class":1142},[518,1343,1146],{"class":1138},[518,1345,1143],{"class":1142},[518,1347,362],{"class":1156},[518,1349,1047],{"class":524},[518,1351,1352],{"class":520,"line":560},[518,1353,557],{"emptyLinePlaceholder":556},[518,1355,1356,1358,1361,1364,1367],{"class":520,"line":580},[518,1357,405],{"class":1015},[518,1359,1360],{"class":1000}," class",[518,1362,1363],{"class":1004}," MyRoleClaims",[518,1365,1366],{"class":1015}," implements",[518,1368,1370],{"class":1369},"sW3Pz"," IdentityClaimsProviderInterface\n",[518,1372,1373],{"class":520,"line":597},[518,1374,1010],{"class":524},[518,1376,1377,1379,1381,1384,1386,1388,1390,1393,1395,1397],{"class":520,"line":614},[518,1378,1016],{"class":1015},[518,1380,1019],{"class":1000},[518,1382,1383],{"class":570}," enrich",[518,1385,660],{"class":524},[518,1387,362],{"class":1044},[518,1389,1031],{"class":524},[518,1391,1392],{"class":528},"identity",[518,1394,1036],{"class":524},[518,1396,509],{"class":532},[518,1398,1399],{"class":1044}," UserIdentity\n",[518,1401,1402],{"class":520,"line":631},[518,1403,1404],{"class":524},"    {\n",[518,1406,1407,1410,1413,1415,1417,1419,1422,1424,1427,1430,1432,1434,1436,1439],{"class":520,"line":648},[518,1408,1409],{"class":524},"        $",[518,1411,1412],{"class":528},"roles ",[518,1414,533],{"class":532},[518,1416,537],{"class":536},[518,1418,540],{"class":532},[518,1420,1421],{"class":528},"roleStore",[518,1423,540],{"class":532},[518,1425,1426],{"class":570},"rolesFor",[518,1428,1429],{"class":524},"($",[518,1431,1392],{"class":528},[518,1433,540],{"class":532},[518,1435,571],{"class":570},[518,1437,1438],{"class":524},"());",[518,1440,1441],{"class":549},"   // list\u003Cstring>\n",[518,1443,1444,1448,1450,1452,1455],{"class":520,"line":682},[518,1445,1447],{"class":1446},"sRxSC","        return",[518,1449,1031],{"class":524},[518,1451,1412],{"class":528},[518,1453,1454],{"class":532},"===",[518,1456,1457],{"class":524}," []\n",[518,1459,1460,1463,1465,1468],{"class":520,"line":709},[518,1461,1462],{"class":532},"            ?",[518,1464,1031],{"class":524},[518,1466,1467],{"class":528},"identity                                           ",[518,1469,1470],{"class":549},"// never fabricate membership\n",[518,1472,1474,1477,1479,1481,1483,1486],{"class":520,"line":1473},11,[518,1475,1476],{"class":532},"            :",[518,1478,1031],{"class":524},[518,1480,1392],{"class":528},[518,1482,540],{"class":532},[518,1484,1485],{"class":570},"withClaims",[518,1487,1488],{"class":524},"([\n",[518,1490,1492,1495,1497,1499,1501,1505,1507,1510,1513,1516,1518,1520,1522,1524,1527,1530,1532,1534],{"class":520,"line":1491},12,[518,1493,1494],{"class":663},"                '",[518,1496,623],{"class":667},[518,1498,664],{"class":663},[518,1500,791],{"class":532},[518,1502,1504],{"class":1503},"sMLJd"," array_values",[518,1506,660],{"class":524},[518,1508,1509],{"class":1503},"array_unique",[518,1511,1512],{"class":524},"([",[518,1514,1515],{"class":532},"...",[518,1517,525],{"class":524},[518,1519,1392],{"class":528},[518,1521,540],{"class":532},[518,1523,623],{"class":570},[518,1525,1526],{"class":524},"(),",[518,1528,1529],{"class":532}," ...",[518,1531,525],{"class":524},[518,1533,623],{"class":528},[518,1535,1536],{"class":524},"])),\n",[518,1538,1540],{"class":520,"line":1539},13,[518,1541,1542],{"class":524},"            ]);\n",[518,1544,1546],{"class":520,"line":1545},14,[518,1547,1548],{"class":524},"    }\n",[518,1550,1552],{"class":520,"line":1551},15,[518,1553,1120],{"class":524},[467,1555,1557],{"className":512,"code":1556,"language":514,"meta":475,"style":475},"// services()\nMyRoleClaims::class => [\n    'class'     => MyRoleClaims::class,\n    'arguments' => ['@' . MyRoleStore::class],\n    'shared'    => true,\n    'tags'      => ['identity.claims_provider'],\n],\n",[307,1558,1559,1564,1577,1595,1624,1638,1660],{"__ignoreMap":475},[518,1560,1561],{"class":520,"line":521},[518,1562,1563],{"class":549},"// services()\n",[518,1565,1566,1569,1571,1573,1575],{"class":520,"line":553},[518,1567,1568],{"class":1044},"MyRoleClaims",[518,1570,1170],{"class":532},[518,1572,1173],{"class":1027},[518,1574,791],{"class":532},[518,1576,794],{"class":524},[518,1578,1579,1581,1583,1585,1587,1589,1591,1593],{"class":520,"line":560},[518,1580,799],{"class":663},[518,1582,1173],{"class":667},[518,1584,664],{"class":663},[518,1586,1188],{"class":532},[518,1588,1363],{"class":1044},[518,1590,1170],{"class":532},[518,1592,1173],{"class":1027},[518,1594,823],{"class":524},[518,1596,1597,1599,1601,1603,1605,1607,1609,1611,1613,1615,1618,1620,1622],{"class":520,"line":580},[518,1598,799],{"class":663},[518,1600,1222],{"class":667},[518,1602,664],{"class":663},[518,1604,791],{"class":532},[518,1606,1229],{"class":524},[518,1608,664],{"class":663},[518,1610,1234],{"class":667},[518,1612,664],{"class":663},[518,1614,1239],{"class":532},[518,1616,1617],{"class":1044}," MyRoleStore",[518,1619,1170],{"class":532},[518,1621,1173],{"class":1027},[518,1623,828],{"class":524},[518,1625,1626,1628,1630,1632,1634,1636],{"class":520,"line":597},[518,1627,799],{"class":663},[518,1629,1204],{"class":667},[518,1631,664],{"class":663},[518,1633,1209],{"class":532},[518,1635,1213],{"class":1212},[518,1637,823],{"class":524},[518,1639,1640,1642,1645,1647,1650,1652,1654,1656,1658],{"class":520,"line":614},[518,1641,799],{"class":663},[518,1643,1644],{"class":667},"tags",[518,1646,664],{"class":663},[518,1648,1649],{"class":532},"      =>",[518,1651,1229],{"class":524},[518,1653,664],{"class":663},[518,1655,1303],{"class":667},[518,1657,664],{"class":663},[518,1659,828],{"class":524},[518,1661,1662],{"class":520,"line":631},[518,1663,828],{"class":524},[293,1665,1666,1667,1669,1670,1673],{},"This is exactly how ",[307,1668,451],{}," adds RBAC role claims. Enrichment is ",[297,1671,1672],{},"additive only"," — a claims provider can grant capabilities, never change who the user is.",[369,1675,1677],{"id":1676},"account-status-gate","Account-status gate",[293,1679,1680,1681,1683,1684,1686],{},"After credentials verify, ",[307,1682,1307],{}," rejects any user whose ",[307,1685,606],{}," isn't permitted to log in:",[467,1688,1690],{"className":512,"code":1689,"language":514,"meta":475,"style":475},"// config/security.php\n'auth' => [\n    'allowed_login_statuses' => ['active'],   // others are rejected at login\n],\n",[307,1691,1692,1697,1710,1735],{"__ignoreMap":475},[518,1693,1694],{"class":520,"line":521},[518,1695,1696],{"class":549},"// config/security.php\n",[518,1698,1699,1701,1704,1706,1708],{"class":520,"line":553},[518,1700,664],{"class":663},[518,1702,1703],{"class":667},"auth",[518,1705,664],{"class":663},[518,1707,791],{"class":532},[518,1709,794],{"class":524},[518,1711,1712,1714,1717,1719,1721,1723,1725,1728,1730,1732],{"class":520,"line":560},[518,1713,799],{"class":663},[518,1715,1716],{"class":667},"allowed_login_statuses",[518,1718,664],{"class":663},[518,1720,791],{"class":532},[518,1722,1229],{"class":524},[518,1724,664],{"class":663},[518,1726,1727],{"class":667},"active",[518,1729,664],{"class":663},[518,1731,1270],{"class":524},[518,1733,1734],{"class":549},"   // others are rejected at login\n",[518,1736,1737],{"class":520,"line":580},[518,1738,828],{"class":524},[369,1740,1742],{"id":1741},"optional-two-factor","Optional: two-factor",[293,1744,1745,1746,1749,1750,1753,1754,1757,1758,1760],{},"If an extension registers a ",[307,1747,1748],{},"TwoFactorServiceInterface"," implementation, the login flow routes through it (",[307,1751,1752],{},"isEnabled()"," / ",[307,1755,1756],{},"beginLogin()","); with none registered, 2FA is skipped entirely. ",[307,1759,323],{}," provides one.",[369,1762,1764],{"id":1763},"next-steps","Next steps",[1766,1767,1768,1774],"ul",{},[1769,1770,1771,1773],"li",{},[316,1772,37],{"href":38}," — the login, token, and refresh flows that sit on top of identity.",[1769,1775,1776,1304,1778,1780,1781,406],{},[316,1777,276],{"href":277},[307,1779,323],{}," store and RBAC via ",[307,1782,451],{},[1784,1785,1786],"style",{},"html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sSBr1, html code.shiki .sSBr1{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#FD971F}html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sQeA1, html code.shiki .sQeA1{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .shWJe, html code.shiki .shWJe{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sv8o3, html code.shiki .sv8o3{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .seZir, html code.shiki .seZir{--shiki-light:#90A4AE;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sMTiH, html code.shiki .sMTiH{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sW3Pz, html code.shiki .sW3Pz{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-font-style:inherit;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-font-style:inherit;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-font-style:italic;--shiki-sepia-text-decoration:underline}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .sMLJd, html code.shiki .sMLJd{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#66D9EF}",{"title":475,"searchDepth":521,"depth":553,"links":1788},[1789,1790,1791,1792,1795,1796,1797,1798,1799],{"id":371,"depth":553,"text":372},{"id":464,"depth":553,"text":465},{"id":490,"depth":553,"text":362},{"id":740,"depth":553,"text":741,"children":1793},[1794],{"id":847,"depth":560,"text":848},{"id":981,"depth":553,"text":982},{"id":1292,"depth":553,"text":1293},{"id":1676,"depth":553,"text":1677},{"id":1741,"depth":553,"text":1742},{"id":1763,"depth":553,"text":1764},"How Glueful models who your users are — the provider-agnostic identity contract, the pluggable user store, and claims.","md",null,{},{"title":41,"description":1800},"WbSlv_0LRzEwfvzeFVUVORMdnxKSnI2vl1W-UtMf8Mo",[1807,1809],{"title":37,"path":38,"stem":39,"description":1808,"children":-1},"Built-in authentication endpoints, token flows, and how to extend them",{"title":45,"path":46,"stem":47,"description":1810,"children":-1},"Read request data and return consistent JSON responses",1780886136033]