[{"data":1,"prerenderedAt":2594},["ShallowReactive",2],{"article-fr-\u002Fsecurity\u002Fauthz\u002Frbac-to-rebac":3,"article-sibling-fr-\u002Fsecurity\u002Fauthz\u002Frbac-to-rebac":1397,"surround-fr-\u002Fsecurity\u002Fauthz\u002Frbac-to-rebac":2560,"related-fr-\u002Fsecurity\u002Fauthz\u002Frbac-to-rebac":2567},{"id":4,"title":5,"body":6,"date":1377,"description":1378,"draft":1379,"extension":1380,"img":1381,"meta":1382,"navigation":123,"path":1383,"seo":1384,"slug":1385,"stem":1386,"tags":1387,"topics":1392,"__hash__":1396},"content\u002F3.security\u002F2.authz\u002F2.rbac-to-rebac.fr.md","De RBAC a ReBAC : migrer un systeme de roles vers OpenFGA sans coupure",{"type":7,"value":8,"toc":1367},"minimark",[9,22,42,47,50,206,209,292,300,321,336,340,351,438,449,453,464,519,536,672,678,686,693,697,700,1027,1034,1044,1048,1055,1125,1128,1194,1201,1204,1210,1214,1217,1247,1254,1258,1280,1301,1315,1321,1325,1360,1363],[10,11,12,13,17,18,21],"p",{},"Le controle d'acces base sur les roles (RBAC) est le modele d'autorisation vers lequel tout le monde se tourne en premier, et pour de bonnes raisons : il est simple, il tient dans quelques tables, et pendant longtemps il suffit exactement. Vous avez des roles, vous les assignez aux utilisateurs, les roles portent des permissions, et un controle est une jointure. Puis un jour, quelqu'un au produit prononce la phrase qui met fin a l'utilite de RBAC : \"laisse le proprietaire partager ",[14,15,16],"em",{},"ce document precis"," avec ",[14,19,20],{},"cette personne precise",".\" Et votre modele de roles bien propre n'a nulle part ou ranger ce fait.",[10,23,24,25,30,31,34,35,41],{},"Ceci est le compagnon pratique de ",[26,27,29],"a",{"href":28},"\u002Fblog\u002Fsecurity\u002Fauthz\u002Fzanzibar-explained","la presentation de Zanzibar",". Celle-la explique ",[14,32,33],{},"pourquoi"," le controle d'acces base sur les relations fonctionne. Celle-ci suppose que le modele vous a convaincu et que vous avez maintenant un vrai systeme RBAC en production a deplacer vers ",[26,36,40],{"href":37,"rel":38},"https:\u002F\u002Fopenfga.dev",[39],"nofollow","OpenFGA"," sans coupure et sans bascule big-bang effrayante. On va mapper les tables, faire tourner les deux systemes cote a cote, backfiller, basculer le chemin de lecture, et seulement ensuite ajouter les fonctionnalites que les roles n'ont jamais pu offrir. En chemin, les pieges qui rendent cela plus dur que les articles ne l'avouent.",[43,44,46],"h2",{"id":45},"la-ou-rbac-atteint-ses-limites","La ou RBAC atteint ses limites",[10,48,49],{},"Voici le RBAC que la plupart des equipes font vraiment tourner. Trois tables et une jointure.",[51,52,57],"pre",{"className":53,"code":54,"language":55,"meta":56,"style":56},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","CREATE TABLE roles (\n  id   SERIAL PRIMARY KEY,\n  name TEXT UNIQUE          -- 'admin', 'editor', 'viewer'\n);\n\nCREATE TABLE user_roles (\n  user_id INT,\n  role_id INT\n);\n\nCREATE TABLE role_permissions (\n  role_id    INT,\n  permission TEXT           -- 'document.edit', 'document.view'\n);\n","sql","",[58,59,60,80,96,112,118,125,137,148,157,162,167,179,189,201],"code",{"__ignoreMap":56},[61,62,65,69,72,76],"span",{"class":63,"line":64},"line",1,[61,66,68],{"class":67},"sbssI","CREATE",[61,70,71],{"class":67}," TABLE",[61,73,75],{"class":74},"s2Zo4"," roles",[61,77,79],{"class":78},"sTEyZ"," (\n",[61,81,83,86,90,93],{"class":63,"line":82},2,[61,84,85],{"class":78},"  id   ",[61,87,89],{"class":88},"spNyl","SERIAL",[61,91,92],{"class":88}," PRIMARY KEY",[61,94,95],{"class":78},",\n",[61,97,99,102,105,108],{"class":63,"line":98},3,[61,100,101],{"class":67},"  name",[61,103,104],{"class":88}," TEXT",[61,106,107],{"class":67}," UNIQUE",[61,109,111],{"class":110},"sHwdD","          -- 'admin', 'editor', 'viewer'\n",[61,113,115],{"class":63,"line":114},4,[61,116,117],{"class":78},");\n",[61,119,121],{"class":63,"line":120},5,[61,122,124],{"emptyLinePlaceholder":123},true,"\n",[61,126,128,130,132,135],{"class":63,"line":127},6,[61,129,68],{"class":67},[61,131,71],{"class":67},[61,133,134],{"class":74}," user_roles",[61,136,79],{"class":78},[61,138,140,143,146],{"class":63,"line":139},7,[61,141,142],{"class":78},"  user_id ",[61,144,145],{"class":88},"INT",[61,147,95],{"class":78},[61,149,151,154],{"class":63,"line":150},8,[61,152,153],{"class":78},"  role_id ",[61,155,156],{"class":88},"INT\n",[61,158,160],{"class":63,"line":159},9,[61,161,117],{"class":78},[61,163,165],{"class":63,"line":164},10,[61,166,124],{"emptyLinePlaceholder":123},[61,168,170,172,174,177],{"class":63,"line":169},11,[61,171,68],{"class":67},[61,173,71],{"class":67},[61,175,176],{"class":74}," role_permissions",[61,178,79],{"class":78},[61,180,182,185,187],{"class":63,"line":181},12,[61,183,184],{"class":78},"  role_id    ",[61,186,145],{"class":88},[61,188,95],{"class":78},[61,190,192,195,198],{"class":63,"line":191},13,[61,193,194],{"class":78},"  permission ",[61,196,197],{"class":88},"TEXT",[61,199,200],{"class":110},"           -- 'document.edit', 'document.view'\n",[61,202,204],{"class":63,"line":203},14,[61,205,117],{"class":78},[10,207,208],{},"Un controle de permission est une seule requete : cet utilisateur detient-il un role qui accorde cette permission ?",[51,210,212],{"className":53,"code":211,"language":55,"meta":56,"style":56},"SELECT EXISTS (\n  SELECT 1 FROM user_roles ur\n  JOIN role_permissions rp ON rp.role_id = ur.role_id\n  WHERE ur.user_id = $1 AND rp.permission = $2\n);\n",[58,213,214,224,238,259,288],{"__ignoreMap":56},[61,215,216,219,222],{"class":63,"line":64},[61,217,218],{"class":67},"SELECT",[61,220,221],{"class":67}," EXISTS",[61,223,79],{"class":78},[61,225,226,229,232,235],{"class":63,"line":82},[61,227,228],{"class":67},"  SELECT",[61,230,231],{"class":67}," 1",[61,233,234],{"class":67}," FROM",[61,236,237],{"class":78}," user_roles ur\n",[61,239,240,243,246,249,252,256],{"class":63,"line":98},[61,241,242],{"class":67},"  JOIN",[61,244,245],{"class":78}," role_permissions rp ",[61,247,248],{"class":67},"ON",[61,250,251],{"class":78}," rp.role_id ",[61,253,255],{"class":254},"sMK4o","=",[61,257,258],{"class":78}," ur.role_id\n",[61,260,261,264,267,269,272,275,278,281,283,285],{"class":63,"line":114},[61,262,263],{"class":67},"  WHERE",[61,265,266],{"class":78}," ur.user_id ",[61,268,255],{"class":254},[61,270,271],{"class":78}," $",[61,273,274],{"class":67},"1",[61,276,277],{"class":67}," AND",[61,279,280],{"class":78}," rp.permission ",[61,282,255],{"class":254},[61,284,271],{"class":78},[61,286,287],{"class":67},"2\n",[61,289,290],{"class":63,"line":120},[61,291,117],{"class":78},[10,293,294,295,299],{},"C'est propre et rapide, et cela repond a exactement une forme de question : ",[296,297,298],"strong",{},"que peut faire cet utilisateur, globalement, en vertu de son role ?"," Le mur que vous heurtez, c'est que les vrais produits arretent de poser cette question. Ils commencent a demander :",[301,302,303,311,314],"ul",{},[304,305,306,307,310],"li",{},"\"Bob peut-il editer ",[14,308,309],{},"le document 42"," ?\" Pas les documents en general. Celui-la. RBAC n'a pas de colonne pour l'objet.",[304,312,313],{},"\"Partage ce dossier avec l'equipe design, et que ca cascade vers chaque fichier a l'interieur.\" RBAC n'a pas de hierarchie.",[304,315,316,317,320],{},"\"Pourquoi Bob voit-il ce fichier ?\" Avec les roles, la reponse est \"il est editor\", ce qui n'explique pas ",[14,318,319],{},"quel"," droit, car les roles sont globaux et les objets ne le sont pas.",[10,322,323,324,327,328,331,332,335],{},"Vous pouvez greffer des ID d'objet sur les chaines de permission (",[58,325,326],{},"document:42:edit",") et regarder les tables exploser, ou accepter que le modele a la mauvaise forme pour l'acces par objet. RBAC repond a ",[14,329,330],{},"quel role"," ; le produit demande desormais ",[14,333,334],{},"quel objet",", et cet ecart est toute la raison de migrer.",[43,337,339],{"id":338},"le-declic-mental-un-role-nest-quune-relation","Le declic mental : un role n'est qu'une relation",[10,341,342,343,346,347,350],{},"L'astuce pour une migration sereine est d'arreter de voir ReBAC comme un autre univers. Ce sont les memes faits, deplaces. En RBAC, \"Bob est admin\" vit comme une ligne dans ",[58,344,345],{},"user_roles",". En ReBAC, cela vit comme un ",[296,348,349],{},"tuple"," : une relation entre Bob et un objet.",[352,353,354,370],"table",{},[355,356,357],"thead",{},[358,359,360,364,367],"tr",{},[361,362,363],"th",{},"Concept RBAC",[361,365,366],{},"Vit comme",[361,368,369],{},"Equivalent ReBAC",[371,372,373,390,408,423],"tbody",{},[358,374,375,381,384],{},[376,377,378],"td",{},[58,379,380],{},"user_roles(bob, admin)",[376,382,383],{},"une ligne",[376,385,386,387],{},"tuple ",[58,388,389],{},"organization:acme#admin@user:bob",[358,391,392,398,401],{},[376,393,394,395],{},"le role ",[58,396,397],{},"admin",[376,399,400],{},"une table",[376,402,403,404,407],{},"une ",[296,405,406],{},"relation"," sur un type",[358,409,410,415,417],{},[376,411,412],{},[58,413,414],{},"role_permissions(admin, doc.edit)",[376,416,383],{},[376,418,403,419,422],{},[296,420,421],{},"regle de reecriture"," dans le modele, pas un tuple",[358,424,425,428,431],{},[376,426,427],{},"le controle de permission (jointure SQL)",[376,429,430],{},"une requete",[376,432,433,434,437],{},"un appel ",[58,435,436],{},"Check"," d'OpenFGA",[10,439,440,441,444,445,448],{},"Deux choses se deplacent differemment, et c'est le noeud. ",[296,442,443],{},"Les assignations de role deviennent des tuples"," (des donnees que vous ecrivez). ",[296,446,447],{},"Les permissions deviennent des regles du modele"," (un schema defini une fois). En RBAC, les deux vivent dans des tables ; en ReBAC, le \"qui a quel role\" est de la donnee et le \"quel role accorde quoi\" est le modele. Mettez ce partage au clair et le reste est mecanique.",[43,450,452],{"id":451},"etape-1-reproduire-votre-rbac-actuel-a-lidentique","Etape 1 : reproduire votre RBAC actuel, a l'identique",[10,454,455,456,459,460,463],{},"N'ajoutez pas encore une seule fonctionnalite. Le premier geste est de modeliser vos roles ",[14,457,458],{},"existants"," dans OpenFGA pour que le comportement soit identique. Les roles globaux se mappent vers des relations sur un seul objet ",[58,461,462],{},"organization"," auquel tout appartient.",[51,465,469],{"className":466,"code":467,"language":468,"meta":56,"style":56},"language-dsl shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","model\n  schema 1.1\n\ntype user\n\ntype organization\n  relations\n    define admin: [user]\n    define editor: [user] or admin\n    define viewer: [user] or editor\n","dsl",[58,470,471,476,481,485,490,494,499,504,509,514],{"__ignoreMap":56},[61,472,473],{"class":63,"line":64},[61,474,475],{},"model\n",[61,477,478],{"class":63,"line":82},[61,479,480],{},"  schema 1.1\n",[61,482,483],{"class":63,"line":98},[61,484,124],{"emptyLinePlaceholder":123},[61,486,487],{"class":63,"line":114},[61,488,489],{},"type user\n",[61,491,492],{"class":63,"line":120},[61,493,124],{"emptyLinePlaceholder":123},[61,495,496],{"class":63,"line":127},[61,497,498],{},"type organization\n",[61,500,501],{"class":63,"line":139},[61,502,503],{},"  relations\n",[61,505,506],{"class":63,"line":150},[61,507,508],{},"    define admin: [user]\n",[61,510,511],{"class":63,"line":159},[61,512,513],{},"    define editor: [user] or admin\n",[61,515,516],{"class":63,"line":164},[61,517,518],{},"    define viewer: [user] or editor\n",[10,520,521,522,524,525,524,528,531,532,535],{},"Notez que les reecritures gagnent deja leur place : ",[58,523,397],{}," implique ",[58,526,527],{},"editor",[58,529,530],{},"viewer",", ce qu'en RBAC vous deviez encoder a la main dans ",[58,533,534],{},"role_permissions",". Vos assignations de role globales deviennent des tuples contre l'org :",[51,537,541],{"className":538,"code":539,"language":540,"meta":56,"style":56},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","[\n  { \"user\": \"user:bob\",   \"relation\": \"admin\",  \"object\": \"organization:acme\" },\n  { \"user\": \"user:carol\", \"relation\": \"viewer\", \"object\": \"organization:acme\" }\n]\n","json",[58,542,543,548,613,667],{"__ignoreMap":56},[61,544,545],{"class":63,"line":64},[61,546,547],{"class":254},"[\n",[61,549,550,553,556,559,562,565,567,571,573,576,579,581,583,585,587,589,591,593,596,599,601,603,605,608,610],{"class":63,"line":82},[61,551,552],{"class":254},"  {",[61,554,555],{"class":254}," \"",[61,557,558],{"class":88},"user",[61,560,561],{"class":254},"\"",[61,563,564],{"class":254},":",[61,566,555],{"class":254},[61,568,570],{"class":569},"sfazB","user:bob",[61,572,561],{"class":254},[61,574,575],{"class":254},",",[61,577,578],{"class":254},"   \"",[61,580,406],{"class":88},[61,582,561],{"class":254},[61,584,564],{"class":254},[61,586,555],{"class":254},[61,588,397],{"class":569},[61,590,561],{"class":254},[61,592,575],{"class":254},[61,594,595],{"class":254},"  \"",[61,597,598],{"class":88},"object",[61,600,561],{"class":254},[61,602,564],{"class":254},[61,604,555],{"class":254},[61,606,607],{"class":569},"organization:acme",[61,609,561],{"class":254},[61,611,612],{"class":254}," },\n",[61,614,615,617,619,621,623,625,627,630,632,634,636,638,640,642,644,646,648,650,652,654,656,658,660,662,664],{"class":63,"line":98},[61,616,552],{"class":254},[61,618,555],{"class":254},[61,620,558],{"class":88},[61,622,561],{"class":254},[61,624,564],{"class":254},[61,626,555],{"class":254},[61,628,629],{"class":569},"user:carol",[61,631,561],{"class":254},[61,633,575],{"class":254},[61,635,555],{"class":254},[61,637,406],{"class":88},[61,639,561],{"class":254},[61,641,564],{"class":254},[61,643,555],{"class":254},[61,645,530],{"class":569},[61,647,561],{"class":254},[61,649,575],{"class":254},[61,651,555],{"class":254},[61,653,598],{"class":88},[61,655,561],{"class":254},[61,657,564],{"class":254},[61,659,555],{"class":254},[61,661,607],{"class":569},[61,663,561],{"class":254},[61,665,666],{"class":254}," }\n",[61,668,669],{"class":63,"line":114},[61,670,671],{"class":254},"]\n",[10,673,674,675,677],{},"Et l'ancien controle de permission devient un ",[58,676,436],{}," :",[51,679,684],{"className":680,"code":682,"language":683},[681],"language-text","check(user:bob, editor, organization:acme)  →  allowed: true   \u002F\u002F admin ⇒ editor\n","text",[58,685,682],{"__ignoreMap":56},[10,687,688,689,692],{},"Le but de cette etape est ",[14,690,691],{},"la parite, pas le progres",". Toute question que votre SQL savait repondre, OpenFGA la repond desormais de la meme facon. Rien ne change dans le produit. Vous avez simplement deplace la verite, ce qui est exactement ce qui rend l'etape suivante sure.",[43,694,696],{"id":695},"etape-2-faire-tourner-openfga-en-shadow","Etape 2 : faire tourner OpenFGA en shadow",[10,698,699],{},"Ne basculez jamais l'autorisation en un seul commit. Faites tourner les deux systemes en parallele et laissez-les se contredire bruyamment avant de faire confiance au nouveau. Gardez le controle SQL comme autorite, et a chaque controle interrogez aussi OpenFGA pour comparer.",[51,701,705],{"className":702,"code":703,"language":704,"meta":56,"style":56},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","func canEdit(ctx context.Context, userID string, docID int) bool {\n    sqlAllowed := sqlCheck(ctx, userID, \"document.edit\") \u002F\u002F toujours la source de verite\n\n    \u002F\u002F shadow : interroger OpenFGA, comparer, mais ne pas encore agir dessus\n    fgaAllowed, err := fga.Check(ctx, fgaReq(userID, \"editor\", \"organization:acme\"))\n    if err != nil {\n        log.Warn(\"fga shadow check failed\", \"err\", err)\n    } else if fgaAllowed != sqlAllowed {\n        log.Warn(\"authz mismatch\",\n            \"user\", userID, \"doc\", docID,\n            \"sql\", sqlAllowed, \"fga\", fgaAllowed) \u002F\u002F enqueter sur chacun\n    }\n\n    return sqlAllowed\n}\n","go",[58,706,707,757,790,794,799,851,867,903,925,944,972,1004,1009,1013,1021],{"__ignoreMap":56},[61,708,709,712,715,718,722,726,729,732,734,737,740,742,745,748,751,754],{"class":63,"line":64},[61,710,711],{"class":254},"func",[61,713,714],{"class":74}," canEdit",[61,716,717],{"class":254},"(",[61,719,721],{"class":720},"sHdIc","ctx",[61,723,725],{"class":724},"sBMFI"," context",[61,727,728],{"class":254},".",[61,730,731],{"class":724},"Context",[61,733,575],{"class":254},[61,735,736],{"class":720}," userID",[61,738,739],{"class":88}," string",[61,741,575],{"class":254},[61,743,744],{"class":720}," docID",[61,746,747],{"class":88}," int",[61,749,750],{"class":254},")",[61,752,753],{"class":88}," bool",[61,755,756],{"class":254}," {\n",[61,758,759,762,765,768,770,772,774,776,778,780,783,785,787],{"class":63,"line":82},[61,760,761],{"class":78},"    sqlAllowed ",[61,763,764],{"class":254},":=",[61,766,767],{"class":74}," sqlCheck",[61,769,717],{"class":254},[61,771,721],{"class":78},[61,773,575],{"class":254},[61,775,736],{"class":78},[61,777,575],{"class":254},[61,779,555],{"class":254},[61,781,782],{"class":569},"document.edit",[61,784,561],{"class":254},[61,786,750],{"class":254},[61,788,789],{"class":110}," \u002F\u002F toujours la source de verite\n",[61,791,792],{"class":63,"line":98},[61,793,124],{"emptyLinePlaceholder":123},[61,795,796],{"class":63,"line":114},[61,797,798],{"class":110},"    \u002F\u002F shadow : interroger OpenFGA, comparer, mais ne pas encore agir dessus\n",[61,800,801,804,806,809,811,814,816,818,820,822,824,827,829,832,834,836,838,840,842,844,846,848],{"class":63,"line":120},[61,802,803],{"class":78},"    fgaAllowed",[61,805,575],{"class":254},[61,807,808],{"class":78}," err ",[61,810,764],{"class":254},[61,812,813],{"class":78}," fga",[61,815,728],{"class":254},[61,817,436],{"class":74},[61,819,717],{"class":254},[61,821,721],{"class":78},[61,823,575],{"class":254},[61,825,826],{"class":74}," fgaReq",[61,828,717],{"class":254},[61,830,831],{"class":78},"userID",[61,833,575],{"class":254},[61,835,555],{"class":254},[61,837,527],{"class":569},[61,839,561],{"class":254},[61,841,575],{"class":254},[61,843,555],{"class":254},[61,845,607],{"class":569},[61,847,561],{"class":254},[61,849,850],{"class":254},"))\n",[61,852,853,857,859,862,865],{"class":63,"line":127},[61,854,856],{"class":855},"s7zQu","    if",[61,858,808],{"class":78},[61,860,861],{"class":254},"!=",[61,863,864],{"class":254}," nil",[61,866,756],{"class":254},[61,868,869,872,874,877,879,881,884,886,888,890,893,895,897,900],{"class":63,"line":139},[61,870,871],{"class":78},"        log",[61,873,728],{"class":254},[61,875,876],{"class":74},"Warn",[61,878,717],{"class":254},[61,880,561],{"class":254},[61,882,883],{"class":569},"fga shadow check failed",[61,885,561],{"class":254},[61,887,575],{"class":254},[61,889,555],{"class":254},[61,891,892],{"class":569},"err",[61,894,561],{"class":254},[61,896,575],{"class":254},[61,898,899],{"class":78}," err",[61,901,902],{"class":254},")\n",[61,904,905,908,911,914,917,919,922],{"class":63,"line":150},[61,906,907],{"class":254},"    }",[61,909,910],{"class":855}," else",[61,912,913],{"class":855}," if",[61,915,916],{"class":78}," fgaAllowed ",[61,918,861],{"class":254},[61,920,921],{"class":78}," sqlAllowed ",[61,923,924],{"class":254},"{\n",[61,926,927,929,931,933,935,937,940,942],{"class":63,"line":159},[61,928,871],{"class":78},[61,930,728],{"class":254},[61,932,876],{"class":74},[61,934,717],{"class":254},[61,936,561],{"class":254},[61,938,939],{"class":569},"authz mismatch",[61,941,561],{"class":254},[61,943,95],{"class":254},[61,945,946,949,951,953,955,957,959,961,964,966,968,970],{"class":63,"line":164},[61,947,948],{"class":254},"            \"",[61,950,558],{"class":569},[61,952,561],{"class":254},[61,954,575],{"class":254},[61,956,736],{"class":78},[61,958,575],{"class":254},[61,960,555],{"class":254},[61,962,963],{"class":569},"doc",[61,965,561],{"class":254},[61,967,575],{"class":254},[61,969,744],{"class":78},[61,971,95],{"class":254},[61,973,974,976,978,980,982,985,987,989,992,994,996,999,1001],{"class":63,"line":169},[61,975,948],{"class":254},[61,977,55],{"class":569},[61,979,561],{"class":254},[61,981,575],{"class":254},[61,983,984],{"class":78}," sqlAllowed",[61,986,575],{"class":254},[61,988,555],{"class":254},[61,990,991],{"class":569},"fga",[61,993,561],{"class":254},[61,995,575],{"class":254},[61,997,998],{"class":78}," fgaAllowed",[61,1000,750],{"class":254},[61,1002,1003],{"class":110}," \u002F\u002F enqueter sur chacun\n",[61,1005,1006],{"class":63,"line":181},[61,1007,1008],{"class":254},"    }\n",[61,1010,1011],{"class":63,"line":191},[61,1012,124],{"emptyLinePlaceholder":123},[61,1014,1015,1018],{"class":63,"line":203},[61,1016,1017],{"class":855},"    return",[61,1019,1020],{"class":78}," sqlAllowed\n",[61,1022,1024],{"class":63,"line":1023},15,[61,1025,1026],{"class":254},"}\n",[10,1028,1029,1030,1033],{},"Laissez cela tourner en production une semaine ou deux. Chaque desaccord journalise est un bug dans votre modele ou dans votre ecriture de tuples, trouve ",[14,1031,1032],{},"avant"," de pouvoir refuser un utilisateur legitime ou fuiter un document. Quand le journal des desaccords devient silencieux, vous avez gagne le droit de faire confiance a OpenFGA. Cette phase shadow est la partie la plus importante de la migration et celle que la plupart des guides sautent.",[10,1035,1036,1037,1040,1041,1043],{},"Pendant le shadow, cablez les ",[296,1038,1039],{},"doubles ecritures"," : partout ou vous inserez aujourd'hui dans ",[58,1042,345],{},", ecrivez aussi le tuple correspondant. Les nouvelles assignations atterrissent desormais dans les deux systemes, donc les deux ne divergent pas pendant que vous preparez la bascule.",[43,1045,1047],{"id":1046},"etape-3-ajouter-ce-que-rbac-na-jamais-pu","Etape 3 : ajouter ce que RBAC n'a jamais pu",[10,1049,1050,1051,1054],{},"Une fois le shadow propre, la recompense. Les fonctionnalites qui ont force cette migration sont maintenant de petits ajouts au modele, sans migration de schema ni explosion de tables. Introduisez un type ",[58,1052,1053],{},"document"," avec des droits par objet et l'heritage de dossier.",[51,1056,1058],{"className":466,"code":1057,"language":468,"meta":56,"style":56},"type group\n  relations\n    define member: [user]\n\ntype folder\n  relations\n    define viewer: [user, group#member]\n\ntype document\n  relations\n    define parent: [folder]\n    define owner:  [user]\n    define editor: [user] or owner\n    define viewer: [user, group#member] or editor or viewer from parent\n",[58,1059,1060,1065,1069,1074,1078,1083,1087,1092,1096,1101,1105,1110,1115,1120],{"__ignoreMap":56},[61,1061,1062],{"class":63,"line":64},[61,1063,1064],{},"type group\n",[61,1066,1067],{"class":63,"line":82},[61,1068,503],{},[61,1070,1071],{"class":63,"line":98},[61,1072,1073],{},"    define member: [user]\n",[61,1075,1076],{"class":63,"line":114},[61,1077,124],{"emptyLinePlaceholder":123},[61,1079,1080],{"class":63,"line":120},[61,1081,1082],{},"type folder\n",[61,1084,1085],{"class":63,"line":127},[61,1086,503],{},[61,1088,1089],{"class":63,"line":139},[61,1090,1091],{},"    define viewer: [user, group#member]\n",[61,1093,1094],{"class":63,"line":150},[61,1095,124],{"emptyLinePlaceholder":123},[61,1097,1098],{"class":63,"line":159},[61,1099,1100],{},"type document\n",[61,1102,1103],{"class":63,"line":164},[61,1104,503],{},[61,1106,1107],{"class":63,"line":169},[61,1108,1109],{},"    define parent: [folder]\n",[61,1111,1112],{"class":63,"line":181},[61,1113,1114],{},"    define owner:  [user]\n",[61,1116,1117],{"class":63,"line":191},[61,1118,1119],{},"    define editor: [user] or owner\n",[61,1121,1122],{"class":63,"line":203},[61,1123,1124],{},"    define viewer: [user, group#member] or editor or viewer from parent\n",[10,1126,1127],{},"Maintenant, la phrase qui cassait RBAC tient en un tuple :",[51,1129,1131],{"className":538,"code":1130,"language":540,"meta":56,"style":56},"[\n  { \"user\": \"user:bob\", \"relation\": \"viewer\", \"object\": \"document:42\" }\n]\n",[58,1132,1133,1137,1190],{"__ignoreMap":56},[61,1134,1135],{"class":63,"line":64},[61,1136,547],{"class":254},[61,1138,1139,1141,1143,1145,1147,1149,1151,1153,1155,1157,1159,1161,1163,1165,1167,1169,1171,1173,1175,1177,1179,1181,1183,1186,1188],{"class":63,"line":82},[61,1140,552],{"class":254},[61,1142,555],{"class":254},[61,1144,558],{"class":88},[61,1146,561],{"class":254},[61,1148,564],{"class":254},[61,1150,555],{"class":254},[61,1152,570],{"class":569},[61,1154,561],{"class":254},[61,1156,575],{"class":254},[61,1158,555],{"class":254},[61,1160,406],{"class":88},[61,1162,561],{"class":254},[61,1164,564],{"class":254},[61,1166,555],{"class":254},[61,1168,530],{"class":569},[61,1170,561],{"class":254},[61,1172,575],{"class":254},[61,1174,555],{"class":254},[61,1176,598],{"class":88},[61,1178,561],{"class":254},[61,1180,564],{"class":254},[61,1182,555],{"class":254},[61,1184,1185],{"class":569},"document:42",[61,1187,561],{"class":254},[61,1189,666],{"class":254},[61,1191,1192],{"class":63,"line":98},[61,1193,671],{"class":254},[10,1195,1196,1197,1200],{},"Bob peut voir le document 42 et rien d'autre. Partagez un dossier avec une equipe et ca cascade vers chaque document a l'interieur via ",[58,1198,1199],{},"viewer from parent",". Ajoutez quelqu'un a un groupe et il herite de chaque droit que le groupe detient. Rien de tout cela n'a demande une table, une migration ou de toucher aux documents. Le meme moteur qui a reproduit vos anciens roles exprime maintenant ce que les roles n'ont jamais pu, car les relations se composent et les lignes de role non.",[10,1202,1203],{},"Votre controle pour un document precis cible desormais cet objet plutot que l'org :",[51,1205,1208],{"className":1206,"code":1207,"language":683},[681],"check(user:bob, viewer, document:42)  →  allowed: true\n",[58,1209,1207],{"__ignoreMap":56},[43,1211,1213],{"id":1212},"etape-4-backfill-verifier-basculer","Etape 4 : backfill, verifier, basculer",[10,1215,1216],{},"La bascule a un ordre strict. Faites-la dans le desordre et vous refusez de vrais utilisateurs ou servez des autorisations perimees.",[1218,1219,1220,1229,1235,1241],"ol",{},[304,1221,1222,1225,1226,1228],{},[296,1223,1224],{},"Backfill."," Script unique : lire chaque ligne ",[58,1227,345],{}," et ecrire le tuple equivalent. Idempotent, car ecrire deux fois le meme tuple est un no-op. Lancez-le, puis reconciliez les comptes.",[304,1230,1231,1234],{},[296,1232,1233],{},"Verifier."," Laissez la comparaison shadow de l'etape 2 continuer contre les donnees backfillees. Zero desaccord sur une fenetre significative est votre feu vert. Ne precipitez pas.",[304,1236,1237,1240],{},[296,1238,1239],{},"Basculer le chemin de lecture."," Changez le controle autoritaire de SQL vers OpenFGA. Le code shadow s'inverse : desormais OpenFGA decide et (brievement) SQL est celui contre lequel vous journalisez, au cas ou.",[304,1242,1243,1246],{},[296,1244,1245],{},"Retirer."," Apres une periode stable, supprimez le controle SQL et, a terme, les tables de roles. Gardez la double ecriture jusqu'a etre certain que rien d'autre ne les lit.",[10,1248,1249,1250,1253],{},"La raison pour laquelle cet ordre importe est ",[26,1251,1252],{"href":28},"la meme preoccupation de coherence que l'article Zanzibar"," : un controle ne doit jamais etre plus perime que la donnee qu'il protege. Pendant le backfill, des tuples sont encore ecrits, donc gardez SQL autoritaire jusqu'a ce que l'ensemble de tuples soit prouve complet. Basculez seulement quand c'est frais.",[43,1255,1257],{"id":1256},"les-pieges-dont-personne-ne-parle","Les pieges dont personne ne parle",[10,1259,1260,1263,1264,1267,1268,1271,1272,1275,1276,1279],{},[296,1261,1262],{},"Ne portez pas votre proliferation de roles."," Si RBAC a grossi jusqu'a quarante roles parce que les roles etaient votre seul outil de cadrage, resistez a recreer quarante relations. Une grande part de cette proliferation venait de gens encodant l'",[14,1265,1266],{},"objet"," et le ",[14,1269,1270],{},"contexte"," dans les noms de roles (",[58,1273,1274],{},"editor_marketing_eu","). En ReBAC, le cadrage par objet est un tuple et le contexte est une condition. Migrez l'",[14,1277,1278],{},"intention",", pas les quarante roles.",[10,1281,1282,1285,1286,1289,1290,1293,1294,1296,1297,1300],{},[296,1283,1284],{},"Verifier un objet est bon marche ; lister ne l'est pas."," ",[58,1287,1288],{},"Check(user, viewer, document:42)"," est rapide. Mais \"montre-moi chaque document que Bob peut voir\" est ",[58,1291,1292],{},"ListObjects",", qui parcourt le graphe a l'envers et est une operation fondamentalement plus lourde. Si votre UI liste les ressources par utilisateur, concevez pour cela tot : OpenFGA a ",[58,1295,1292],{},", mais comprenez son cout et mettez en cache en consequence. Le ",[58,1298,1299],{},"WHERE role IN (...)"," de RBAC etait bon marche ici ; ne supposez pas la parite.",[10,1302,1303,1306,1307,1310,1311,1314],{},[296,1304,1305],{},"Gardez la base de donnees et les tuples synchronises, de facon transactionnelle."," Le mode d'echec des systemes doubles est la derive : vous committez une ligne et l'ecriture du tuple echoue, donc les deux se contredisent. Ecrivez le tuple dans la ",[296,1308,1309],{},"meme transaction"," que le changement metier quand vous le pouvez, ou utilisez un ",[296,1312,1313],{},"outbox"," (enregistrez l'intention dans votre base, un worker la synchronise vers OpenFGA avec des retries). Une ecriture de tuple en fire-and-forget finira par se desynchroniser et produira exactement les tickets \"pourquoi Bob ne voit-il pas ceci ?\" que vous avez migre pour eviter.",[10,1316,1317,1320],{},[296,1318,1319],{},"Modeliser, puis migrer."," Les plus gros gains et les plus grosses erreurs se produisent tous deux dans le fichier de modele, avant qu'un seul tuple ne bouge. Prenez le temps de bien poser les types et les reecritures ; un mauvais modele backfille dans des millions de tuples coute cher a defaire.",[43,1322,1324],{"id":1323},"a-retenir","A retenir",[301,1326,1327,1333,1339,1345,1351],{},[304,1328,1329,1332],{},[296,1330,1331],{},"RBAC atteint ses limites a l'acces par objet."," Le jour ou vous devez partager une ressource avec une personne, les roles n'ont nulle part ou le stocker. Ce besoin est le declencheur de la migration, pas un confort.",[304,1334,1335,1338],{},[296,1336,1337],{},"Un role n'est qu'une relation, et une permission n'est qu'une regle du modele."," Les assignations deviennent des tuples (donnees) ; le \"quel role accorde quoi\" devient des reecritures (schema). Garder ce partage clair rend le mapping mecanique.",[304,1340,1341,1344],{},[296,1342,1343],{},"Reproduire avant d'ameliorer."," Modelisez d'abord vos roles existants et prouvez la parite, pour que la migration soit un deplacement, pas une reecriture. Seulement ensuite ajoutez hierarchie et partage par objet.",[304,1346,1347,1350],{},[296,1348,1349],{},"Shadow, puis basculer dans l'ordre."," Faites tourner OpenFGA a cote de SQL jusqu'a ce que les desaccords se taisent, backfillez, verifiez, basculez, retirez. Jamais en un seul commit.",[304,1352,1353,1356,1357,1359],{},[296,1354,1355],{},"Surveillez les trois pieges :"," ne portez pas la proliferation de roles, budgetez pour ",[58,1358,1292],{},", et gardez tuples et lignes synchronises avec une transaction ou un outbox.",[10,1361,1362],{},"RBAC n'est pas faux ; c'est un modele qui repond bien a une question et s'epuise quand les questions changent de forme. ReBAC ne remplace pas l'idee de role, il la generalise : un role devient une sorte de relation parmi d'autres, et la migration est surtout le travail d'admettre que vos permissions ont toujours ete un graphe, et de finir par les stocker comme tel.",[1364,1365,1366],"style",{},"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 pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":56,"searchDepth":82,"depth":82,"links":1368},[1369,1370,1371,1372,1373,1374,1375,1376],{"id":45,"depth":82,"text":46},{"id":338,"depth":82,"text":339},{"id":451,"depth":82,"text":452},{"id":695,"depth":82,"text":696},{"id":1046,"depth":82,"text":1047},{"id":1212,"depth":82,"text":1213},{"id":1256,"depth":82,"text":1257},{"id":1323,"depth":82,"text":1324},"2026-07-19","Les roles marchent, jusqu'au jour ou quelqu'un dit 'partage juste ce document avec juste cette personne'. Ce jour-la, RBAC atteint ses limites. Voici la migration pratique : mapper les tables de roles vers des tuples de relation, faire tourner OpenFGA en shadow a cote de vos controles SQL, backfiller sans risque, puis seulement debloquer le partage par objet et la hierarchie que les roles n'ont jamais pu offrir. Avec les pieges dont personne ne parle.",false,"md",null,{},"\u002Fsecurity\u002Fauthz\u002Frbac-to-rebac.fr",{"title":5,"description":1378},"de-rbac-a-rebac-migration-openfga","3.security\u002F2.authz\u002F2.rbac-to-rebac.fr",[1388,1389,1390,40,1391],"Authorization","ReBAC","RBAC","Migration",[1393,1394,1395],"authz","backend","platform-engineering","HkC3yO3DM7ELaS1VqMv7we_1a6PF3Xr6Z3R2JET0hXw",{"id":1398,"title":1399,"body":1400,"date":1377,"description":2551,"draft":1379,"extension":1380,"img":1381,"meta":2552,"navigation":123,"path":2553,"seo":2554,"slug":2555,"stem":2556,"tags":2557,"topics":2558,"__hash__":2559},"content\u002F3.security\u002F2.authz\u002F2.rbac-to-rebac.md","From RBAC to ReBAC: Migrating a Role System to OpenFGA Without Downtime",{"type":7,"value":1401,"toc":2541},[1402,1413,1428,1432,1435,1539,1542,1604,1611,1630,1644,1648,1657,1731,1742,1746,1756,1800,1814,1930,1935,1940,1947,1951,1954,2226,2233,2243,2247,2253,2313,2316,2380,2386,2389,2394,2398,2401,2429,2436,2440,2460,2477,2490,2496,2500,2535,2538],[10,1403,1404,1405,1408,1409,1412],{},"Role-based access control is the authorization model everyone reaches for first, and for good reason: it is simple, it fits in a few tables, and for a long time it is exactly enough. You have roles, you assign them to users, roles carry permissions, and a check is a join. Then one day someone in product says the sentence that ends RBAC's usefulness: \"let the owner share ",[14,1406,1407],{},"this one document"," with ",[14,1410,1411],{},"this one person",".\" And your clean role model has no place to put that fact.",[10,1414,1415,1416,1419,1420,1423,1424,1427],{},"This is the practical companion to ",[26,1417,1418],{"href":28},"the Zanzibar walkthrough",". That one explains ",[14,1421,1422],{},"why"," relationship-based access control works. This one assumes you are sold on the model and now have a real RBAC system in production that you need to move to ",[26,1425,40],{"href":37,"rel":1426},[39]," without an outage and without a scary big-bang cutover. We will map the tables, run both systems side by side, backfill, flip the read path, and only then add the features roles never could. Along the way, the traps that make this harder than the blog posts admit.",[43,1429,1431],{"id":1430},"where-rbac-runs-out-of-road","Where RBAC runs out of road",[10,1433,1434],{},"Here is the RBAC most teams actually run. Three tables and a join.",[51,1436,1437],{"className":53,"code":54,"language":55,"meta":56,"style":56},[58,1438,1439,1449,1459,1469,1473,1477,1487,1495,1501,1505,1509,1519,1527,1535],{"__ignoreMap":56},[61,1440,1441,1443,1445,1447],{"class":63,"line":64},[61,1442,68],{"class":67},[61,1444,71],{"class":67},[61,1446,75],{"class":74},[61,1448,79],{"class":78},[61,1450,1451,1453,1455,1457],{"class":63,"line":82},[61,1452,85],{"class":78},[61,1454,89],{"class":88},[61,1456,92],{"class":88},[61,1458,95],{"class":78},[61,1460,1461,1463,1465,1467],{"class":63,"line":98},[61,1462,101],{"class":67},[61,1464,104],{"class":88},[61,1466,107],{"class":67},[61,1468,111],{"class":110},[61,1470,1471],{"class":63,"line":114},[61,1472,117],{"class":78},[61,1474,1475],{"class":63,"line":120},[61,1476,124],{"emptyLinePlaceholder":123},[61,1478,1479,1481,1483,1485],{"class":63,"line":127},[61,1480,68],{"class":67},[61,1482,71],{"class":67},[61,1484,134],{"class":74},[61,1486,79],{"class":78},[61,1488,1489,1491,1493],{"class":63,"line":139},[61,1490,142],{"class":78},[61,1492,145],{"class":88},[61,1494,95],{"class":78},[61,1496,1497,1499],{"class":63,"line":150},[61,1498,153],{"class":78},[61,1500,156],{"class":88},[61,1502,1503],{"class":63,"line":159},[61,1504,117],{"class":78},[61,1506,1507],{"class":63,"line":164},[61,1508,124],{"emptyLinePlaceholder":123},[61,1510,1511,1513,1515,1517],{"class":63,"line":169},[61,1512,68],{"class":67},[61,1514,71],{"class":67},[61,1516,176],{"class":74},[61,1518,79],{"class":78},[61,1520,1521,1523,1525],{"class":63,"line":181},[61,1522,184],{"class":78},[61,1524,145],{"class":88},[61,1526,95],{"class":78},[61,1528,1529,1531,1533],{"class":63,"line":191},[61,1530,194],{"class":78},[61,1532,197],{"class":88},[61,1534,200],{"class":110},[61,1536,1537],{"class":63,"line":203},[61,1538,117],{"class":78},[10,1540,1541],{},"A permission check is one query: does this user hold any role that grants this permission?",[51,1543,1544],{"className":53,"code":211,"language":55,"meta":56,"style":56},[58,1545,1546,1554,1564,1578,1600],{"__ignoreMap":56},[61,1547,1548,1550,1552],{"class":63,"line":64},[61,1549,218],{"class":67},[61,1551,221],{"class":67},[61,1553,79],{"class":78},[61,1555,1556,1558,1560,1562],{"class":63,"line":82},[61,1557,228],{"class":67},[61,1559,231],{"class":67},[61,1561,234],{"class":67},[61,1563,237],{"class":78},[61,1565,1566,1568,1570,1572,1574,1576],{"class":63,"line":98},[61,1567,242],{"class":67},[61,1569,245],{"class":78},[61,1571,248],{"class":67},[61,1573,251],{"class":78},[61,1575,255],{"class":254},[61,1577,258],{"class":78},[61,1579,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598],{"class":63,"line":114},[61,1581,263],{"class":67},[61,1583,266],{"class":78},[61,1585,255],{"class":254},[61,1587,271],{"class":78},[61,1589,274],{"class":67},[61,1591,277],{"class":67},[61,1593,280],{"class":78},[61,1595,255],{"class":254},[61,1597,271],{"class":78},[61,1599,287],{"class":67},[61,1601,1602],{"class":63,"line":120},[61,1603,117],{"class":78},[10,1605,1606,1607,1610],{},"This is clean and fast, and it answers exactly one shape of question: ",[296,1608,1609],{},"what can this user do, globally, by virtue of their role?"," The wall you hit is that real products stop asking that question. They start asking:",[301,1612,1613,1620,1623],{},[304,1614,1615,1616,1619],{},"\"Can Bob edit ",[14,1617,1618],{},"document 42","?\" Not documents in general. That one. RBAC has no column for the object.",[304,1621,1622],{},"\"Share this folder with the design team, and let that cascade to every file inside.\" RBAC has no hierarchy.",[304,1624,1625,1626,1629],{},"\"Why can Bob see this file?\" With roles the answer is \"he's an editor,\" which does not explain ",[14,1627,1628],{},"which"," grant, because roles are global and objects are not.",[10,1631,1632,1633,1635,1636,1639,1640,1643],{},"You can bolt object IDs onto the permission strings (",[58,1634,326],{},") and watch the tables explode, or you can accept that the model is wrong-shaped for per-object access. RBAC answers ",[14,1637,1638],{},"which role","; the product is now asking ",[14,1641,1642],{},"which object",", and that gap is the entire reason to migrate.",[43,1645,1647],{"id":1646},"the-mental-shift-a-role-is-just-a-relation","The mental shift: a role is just a relation",[10,1649,1650,1651,1653,1654,1656],{},"The trick to a calm migration is to stop thinking of ReBAC as a different universe. It is the same facts, relocated. In RBAC, \"Bob is an admin\" lives as a row in ",[58,1652,345],{},". In ReBAC, it lives as a ",[296,1655,349],{},": a relation between Bob and an object.",[352,1658,1659,1672],{},[355,1660,1661],{},[358,1662,1663,1666,1669],{},[361,1664,1665],{},"RBAC concept",[361,1667,1668],{},"Lives as",[361,1670,1671],{},"ReBAC equivalent",[371,1673,1674,1687,1703,1717],{},[358,1675,1676,1680,1683],{},[376,1677,1678],{},[58,1679,380],{},[376,1681,1682],{},"a row",[376,1684,386,1685],{},[58,1686,389],{},[358,1688,1689,1694,1697],{},[376,1690,1691,1692],{},"the role ",[58,1693,397],{},[376,1695,1696],{},"a table",[376,1698,1699,1700,1702],{},"a ",[296,1701,406],{}," on a type",[358,1704,1705,1709,1711],{},[376,1706,1707],{},[58,1708,414],{},[376,1710,1682],{},[376,1712,1699,1713,1716],{},[296,1714,1715],{},"rewrite rule"," in the model, not a tuple",[358,1718,1719,1722,1725],{},[376,1720,1721],{},"the permission check (SQL join)",[376,1723,1724],{},"a query",[376,1726,1727,1728,1730],{},"an OpenFGA ",[58,1729,436],{}," call",[10,1732,1733,1734,1737,1738,1741],{},"Two things move differently, and this is the crux. ",[296,1735,1736],{},"Role assignments become tuples"," (data you write). ",[296,1739,1740],{},"Permissions become model rules"," (schema you define once). In RBAC both live in tables; in ReBAC the \"who has which role\" is data and the \"which role grants what\" is the model. Get that split straight and the rest is mechanical.",[43,1743,1745],{"id":1744},"step-1-reproduce-your-current-rbac-exactly","Step 1: reproduce your current RBAC, exactly",[10,1747,1748,1749,1752,1753,1755],{},"Do not add a single feature yet. The first move is to model your ",[14,1750,1751],{},"existing"," roles in OpenFGA so behavior is identical. Global roles map to relations on a single ",[58,1754,462],{}," object that everything belongs to.",[51,1757,1758],{"className":466,"code":467,"language":468,"meta":56,"style":56},[58,1759,1760,1764,1768,1772,1776,1780,1784,1788,1792,1796],{"__ignoreMap":56},[61,1761,1762],{"class":63,"line":64},[61,1763,475],{},[61,1765,1766],{"class":63,"line":82},[61,1767,480],{},[61,1769,1770],{"class":63,"line":98},[61,1771,124],{"emptyLinePlaceholder":123},[61,1773,1774],{"class":63,"line":114},[61,1775,489],{},[61,1777,1778],{"class":63,"line":120},[61,1779,124],{"emptyLinePlaceholder":123},[61,1781,1782],{"class":63,"line":127},[61,1783,498],{},[61,1785,1786],{"class":63,"line":139},[61,1787,503],{},[61,1789,1790],{"class":63,"line":150},[61,1791,508],{},[61,1793,1794],{"class":63,"line":159},[61,1795,513],{},[61,1797,1798],{"class":63,"line":164},[61,1799,518],{},[10,1801,1802,1803,1805,1806,1805,1808,1810,1811,1813],{},"Note the rewrites already earn their keep: ",[58,1804,397],{}," implies ",[58,1807,527],{},[58,1809,530],{},", which in RBAC you had to encode by hand in ",[58,1812,534],{},". Your global role assignments become tuples against the org:",[51,1815,1816],{"className":538,"code":539,"language":540,"meta":56,"style":56},[58,1817,1818,1822,1874,1926],{"__ignoreMap":56},[61,1819,1820],{"class":63,"line":64},[61,1821,547],{"class":254},[61,1823,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870,1872],{"class":63,"line":82},[61,1825,552],{"class":254},[61,1827,555],{"class":254},[61,1829,558],{"class":88},[61,1831,561],{"class":254},[61,1833,564],{"class":254},[61,1835,555],{"class":254},[61,1837,570],{"class":569},[61,1839,561],{"class":254},[61,1841,575],{"class":254},[61,1843,578],{"class":254},[61,1845,406],{"class":88},[61,1847,561],{"class":254},[61,1849,564],{"class":254},[61,1851,555],{"class":254},[61,1853,397],{"class":569},[61,1855,561],{"class":254},[61,1857,575],{"class":254},[61,1859,595],{"class":254},[61,1861,598],{"class":88},[61,1863,561],{"class":254},[61,1865,564],{"class":254},[61,1867,555],{"class":254},[61,1869,607],{"class":569},[61,1871,561],{"class":254},[61,1873,612],{"class":254},[61,1875,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924],{"class":63,"line":98},[61,1877,552],{"class":254},[61,1879,555],{"class":254},[61,1881,558],{"class":88},[61,1883,561],{"class":254},[61,1885,564],{"class":254},[61,1887,555],{"class":254},[61,1889,629],{"class":569},[61,1891,561],{"class":254},[61,1893,575],{"class":254},[61,1895,555],{"class":254},[61,1897,406],{"class":88},[61,1899,561],{"class":254},[61,1901,564],{"class":254},[61,1903,555],{"class":254},[61,1905,530],{"class":569},[61,1907,561],{"class":254},[61,1909,575],{"class":254},[61,1911,555],{"class":254},[61,1913,598],{"class":88},[61,1915,561],{"class":254},[61,1917,564],{"class":254},[61,1919,555],{"class":254},[61,1921,607],{"class":569},[61,1923,561],{"class":254},[61,1925,666],{"class":254},[61,1927,1928],{"class":63,"line":114},[61,1929,671],{"class":254},[10,1931,1932,1933,564],{},"And the old permission check becomes a ",[58,1934,436],{},[51,1936,1938],{"className":1937,"code":682,"language":683},[681],[58,1939,682],{"__ignoreMap":56},[10,1941,1942,1943,1946],{},"The point of this step is ",[14,1944,1945],{},"parity, not progress",". Every question your SQL could answer, OpenFGA now answers the same way. Nothing in the product changes. You have simply relocated the truth, which is exactly what makes the next step safe.",[43,1948,1950],{"id":1949},"step-2-run-openfga-in-shadow","Step 2: run OpenFGA in shadow",[10,1952,1953],{},"Never flip authorization in one commit. Run both systems in parallel and let them disagree loudly before you trust the new one. Keep the SQL check authoritative, and on every check also ask OpenFGA and compare.",[51,1955,1957],{"className":702,"code":1956,"language":704,"meta":56,"style":56},"func canEdit(ctx context.Context, userID string, docID int) bool {\n    sqlAllowed := sqlCheck(ctx, userID, \"document.edit\") \u002F\u002F still the source of truth\n\n    \u002F\u002F shadow: ask OpenFGA, compare, but do not act on it yet\n    fgaAllowed, err := fga.Check(ctx, fgaReq(userID, \"editor\", \"organization:acme\"))\n    if err != nil {\n        log.Warn(\"fga shadow check failed\", \"err\", err)\n    } else if fgaAllowed != sqlAllowed {\n        log.Warn(\"authz mismatch\",\n            \"user\", userID, \"doc\", docID,\n            \"sql\", sqlAllowed, \"fga\", fgaAllowed) \u002F\u002F investigate every one\n    }\n\n    return sqlAllowed\n}\n",[58,1958,1959,1993,2022,2026,2031,2077,2089,2119,2135,2153,2179,2208,2212,2216,2222],{"__ignoreMap":56},[61,1960,1961,1963,1965,1967,1969,1971,1973,1975,1977,1979,1981,1983,1985,1987,1989,1991],{"class":63,"line":64},[61,1962,711],{"class":254},[61,1964,714],{"class":74},[61,1966,717],{"class":254},[61,1968,721],{"class":720},[61,1970,725],{"class":724},[61,1972,728],{"class":254},[61,1974,731],{"class":724},[61,1976,575],{"class":254},[61,1978,736],{"class":720},[61,1980,739],{"class":88},[61,1982,575],{"class":254},[61,1984,744],{"class":720},[61,1986,747],{"class":88},[61,1988,750],{"class":254},[61,1990,753],{"class":88},[61,1992,756],{"class":254},[61,1994,1995,1997,1999,2001,2003,2005,2007,2009,2011,2013,2015,2017,2019],{"class":63,"line":82},[61,1996,761],{"class":78},[61,1998,764],{"class":254},[61,2000,767],{"class":74},[61,2002,717],{"class":254},[61,2004,721],{"class":78},[61,2006,575],{"class":254},[61,2008,736],{"class":78},[61,2010,575],{"class":254},[61,2012,555],{"class":254},[61,2014,782],{"class":569},[61,2016,561],{"class":254},[61,2018,750],{"class":254},[61,2020,2021],{"class":110}," \u002F\u002F still the source of truth\n",[61,2023,2024],{"class":63,"line":98},[61,2025,124],{"emptyLinePlaceholder":123},[61,2027,2028],{"class":63,"line":114},[61,2029,2030],{"class":110},"    \u002F\u002F shadow: ask OpenFGA, compare, but do not act on it yet\n",[61,2032,2033,2035,2037,2039,2041,2043,2045,2047,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073,2075],{"class":63,"line":120},[61,2034,803],{"class":78},[61,2036,575],{"class":254},[61,2038,808],{"class":78},[61,2040,764],{"class":254},[61,2042,813],{"class":78},[61,2044,728],{"class":254},[61,2046,436],{"class":74},[61,2048,717],{"class":254},[61,2050,721],{"class":78},[61,2052,575],{"class":254},[61,2054,826],{"class":74},[61,2056,717],{"class":254},[61,2058,831],{"class":78},[61,2060,575],{"class":254},[61,2062,555],{"class":254},[61,2064,527],{"class":569},[61,2066,561],{"class":254},[61,2068,575],{"class":254},[61,2070,555],{"class":254},[61,2072,607],{"class":569},[61,2074,561],{"class":254},[61,2076,850],{"class":254},[61,2078,2079,2081,2083,2085,2087],{"class":63,"line":127},[61,2080,856],{"class":855},[61,2082,808],{"class":78},[61,2084,861],{"class":254},[61,2086,864],{"class":254},[61,2088,756],{"class":254},[61,2090,2091,2093,2095,2097,2099,2101,2103,2105,2107,2109,2111,2113,2115,2117],{"class":63,"line":139},[61,2092,871],{"class":78},[61,2094,728],{"class":254},[61,2096,876],{"class":74},[61,2098,717],{"class":254},[61,2100,561],{"class":254},[61,2102,883],{"class":569},[61,2104,561],{"class":254},[61,2106,575],{"class":254},[61,2108,555],{"class":254},[61,2110,892],{"class":569},[61,2112,561],{"class":254},[61,2114,575],{"class":254},[61,2116,899],{"class":78},[61,2118,902],{"class":254},[61,2120,2121,2123,2125,2127,2129,2131,2133],{"class":63,"line":150},[61,2122,907],{"class":254},[61,2124,910],{"class":855},[61,2126,913],{"class":855},[61,2128,916],{"class":78},[61,2130,861],{"class":254},[61,2132,921],{"class":78},[61,2134,924],{"class":254},[61,2136,2137,2139,2141,2143,2145,2147,2149,2151],{"class":63,"line":159},[61,2138,871],{"class":78},[61,2140,728],{"class":254},[61,2142,876],{"class":74},[61,2144,717],{"class":254},[61,2146,561],{"class":254},[61,2148,939],{"class":569},[61,2150,561],{"class":254},[61,2152,95],{"class":254},[61,2154,2155,2157,2159,2161,2163,2165,2167,2169,2171,2173,2175,2177],{"class":63,"line":164},[61,2156,948],{"class":254},[61,2158,558],{"class":569},[61,2160,561],{"class":254},[61,2162,575],{"class":254},[61,2164,736],{"class":78},[61,2166,575],{"class":254},[61,2168,555],{"class":254},[61,2170,963],{"class":569},[61,2172,561],{"class":254},[61,2174,575],{"class":254},[61,2176,744],{"class":78},[61,2178,95],{"class":254},[61,2180,2181,2183,2185,2187,2189,2191,2193,2195,2197,2199,2201,2203,2205],{"class":63,"line":169},[61,2182,948],{"class":254},[61,2184,55],{"class":569},[61,2186,561],{"class":254},[61,2188,575],{"class":254},[61,2190,984],{"class":78},[61,2192,575],{"class":254},[61,2194,555],{"class":254},[61,2196,991],{"class":569},[61,2198,561],{"class":254},[61,2200,575],{"class":254},[61,2202,998],{"class":78},[61,2204,750],{"class":254},[61,2206,2207],{"class":110}," \u002F\u002F investigate every one\n",[61,2209,2210],{"class":63,"line":181},[61,2211,1008],{"class":254},[61,2213,2214],{"class":63,"line":191},[61,2215,124],{"emptyLinePlaceholder":123},[61,2217,2218,2220],{"class":63,"line":203},[61,2219,1017],{"class":855},[61,2221,1020],{"class":78},[61,2223,2224],{"class":63,"line":1023},[61,2225,1026],{"class":254},[10,2227,2228,2229,2232],{},"Let this run in production for a week or two. Every logged mismatch is a bug in your model or your tuple-writing, found ",[14,2230,2231],{},"before"," it can deny a legitimate user or leak a document. When the mismatch log goes quiet, you have earned the right to trust OpenFGA. This shadow phase is the single most important part of the migration and the one most guides skip.",[10,2234,2235,2236,2239,2240,2242],{},"While shadowing, wire up ",[296,2237,2238],{},"dual writes",": anywhere you currently insert into ",[58,2241,345],{},", also write the corresponding tuple. New assignments now land in both systems, so the two do not drift while you prepare the cutover.",[43,2244,2246],{"id":2245},"step-3-add-what-rbac-never-could","Step 3: add what RBAC never could",[10,2248,2249,2250,2252],{},"Once shadow is clean, the payoff. The features that forced this migration are now small additions to the model, with no schema migration and no table explosion. Introduce a ",[58,2251,1053],{}," type with per-object grants and folder inheritance.",[51,2254,2255],{"className":466,"code":1057,"language":468,"meta":56,"style":56},[58,2256,2257,2261,2265,2269,2273,2277,2281,2285,2289,2293,2297,2301,2305,2309],{"__ignoreMap":56},[61,2258,2259],{"class":63,"line":64},[61,2260,1064],{},[61,2262,2263],{"class":63,"line":82},[61,2264,503],{},[61,2266,2267],{"class":63,"line":98},[61,2268,1073],{},[61,2270,2271],{"class":63,"line":114},[61,2272,124],{"emptyLinePlaceholder":123},[61,2274,2275],{"class":63,"line":120},[61,2276,1082],{},[61,2278,2279],{"class":63,"line":127},[61,2280,503],{},[61,2282,2283],{"class":63,"line":139},[61,2284,1091],{},[61,2286,2287],{"class":63,"line":150},[61,2288,124],{"emptyLinePlaceholder":123},[61,2290,2291],{"class":63,"line":159},[61,2292,1100],{},[61,2294,2295],{"class":63,"line":164},[61,2296,503],{},[61,2298,2299],{"class":63,"line":169},[61,2300,1109],{},[61,2302,2303],{"class":63,"line":181},[61,2304,1114],{},[61,2306,2307],{"class":63,"line":191},[61,2308,1119],{},[61,2310,2311],{"class":63,"line":203},[61,2312,1124],{},[10,2314,2315],{},"Now the sentence that broke RBAC is one tuple:",[51,2317,2318],{"className":538,"code":1130,"language":540,"meta":56,"style":56},[58,2319,2320,2324,2376],{"__ignoreMap":56},[61,2321,2322],{"class":63,"line":64},[61,2323,547],{"class":254},[61,2325,2326,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346,2348,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374],{"class":63,"line":82},[61,2327,552],{"class":254},[61,2329,555],{"class":254},[61,2331,558],{"class":88},[61,2333,561],{"class":254},[61,2335,564],{"class":254},[61,2337,555],{"class":254},[61,2339,570],{"class":569},[61,2341,561],{"class":254},[61,2343,575],{"class":254},[61,2345,555],{"class":254},[61,2347,406],{"class":88},[61,2349,561],{"class":254},[61,2351,564],{"class":254},[61,2353,555],{"class":254},[61,2355,530],{"class":569},[61,2357,561],{"class":254},[61,2359,575],{"class":254},[61,2361,555],{"class":254},[61,2363,598],{"class":88},[61,2365,561],{"class":254},[61,2367,564],{"class":254},[61,2369,555],{"class":254},[61,2371,1185],{"class":569},[61,2373,561],{"class":254},[61,2375,666],{"class":254},[61,2377,2378],{"class":63,"line":98},[61,2379,671],{"class":254},[10,2381,2382,2383,2385],{},"Bob can view document 42 and nothing else. Share a folder with a team and it cascades to every document inside via ",[58,2384,1199],{},". Add someone to a group and they inherit every grant that group holds. None of this required a table, a migration, or touching the documents. The same engine that reproduced your old roles now expresses the things roles never could, because relationships compose and role rows do not.",[10,2387,2388],{},"Your check for a specific document now targets that object instead of the org:",[51,2390,2392],{"className":2391,"code":1207,"language":683},[681],[58,2393,1207],{"__ignoreMap":56},[43,2395,2397],{"id":2396},"step-4-backfill-verify-cut-over","Step 4: backfill, verify, cut over",[10,2399,2400],{},"The cutover has a strict order. Do it out of order and you either deny real users or serve stale allows.",[1218,2402,2403,2411,2417,2423],{},[304,2404,2405,2407,2408,2410],{},[296,2406,1224],{}," One-time script: read every ",[58,2409,345],{}," row and write the equivalent tuple. Idempotent, because writing the same tuple twice is a no-op. Run it, then reconcile counts.",[304,2412,2413,2416],{},[296,2414,2415],{},"Verify."," Let the shadow comparison from Step 2 keep running against the backfilled data. Zero mismatches over a meaningful window is your green light. Do not rush this.",[304,2418,2419,2422],{},[296,2420,2421],{},"Flip the read path."," Change the authoritative check from SQL to OpenFGA. The shadow code inverts: now OpenFGA decides and (briefly) SQL is the one you log against, just in case.",[304,2424,2425,2428],{},[296,2426,2427],{},"Remove."," After a stable period, delete the SQL check and, eventually, the role tables. Keep the dual-write until you are certain nothing else reads them.",[10,2430,2431,2432,2435],{},"The reason this order matters is the ",[26,2433,2434],{"href":28},"same consistency concern from the Zanzibar post",": a check must never be staler than the data it guards. During backfill, tuples are still being written, so keep SQL authoritative until the tuple set is provably complete. Flip only when fresh.",[43,2437,2439],{"id":2438},"the-traps-nobody-warns-you-about","The traps nobody warns you about",[10,2441,2442,2445,2446,2448,2449,2452,2453,2455,2456,2459],{},[296,2443,2444],{},"Do not port your role sprawl."," If RBAC grew to forty roles because roles were your only tool for scoping, resist recreating forty relations. Most of that sprawl was people encoding ",[14,2447,598],{}," and ",[14,2450,2451],{},"context"," into role names (",[58,2454,1274],{},"). In ReBAC, object scoping is a tuple and context is a condition. Migrate the ",[14,2457,2458],{},"intent",", not the forty roles.",[10,2461,2462,1285,2465,2467,2468,2470,2471,2473,2474,2476],{},[296,2463,2464],{},"Checking one object is cheap; listing is not.",[58,2466,1288],{}," is fast. But \"show me every document Bob can view\" is ",[58,2469,1292],{},", which walks the graph in reverse and is a fundamentally heavier operation. If your UI lists resources per user, design for it early: OpenFGA has ",[58,2472,1292],{},", but understand its cost and cache accordingly. RBAC's ",[58,2475,1299],{}," was cheap here; do not assume parity.",[10,2478,2479,2482,2483,2486,2487,2489],{},[296,2480,2481],{},"Keep the database and tuples in sync, transactionally."," The failure mode of dual systems is drift: you commit a row and the tuple write fails, so the two disagree. Write the tuple in the ",[296,2484,2485],{},"same transaction"," as the domain change where you can, or use an ",[296,2488,1313],{}," (record the intent in your DB, a worker syncs it to OpenFGA with retries). A tuple write that is fire-and-forget will eventually desync and produce exactly the \"why can't Bob see this?\" tickets you migrated to avoid.",[10,2491,2492,2495],{},[296,2493,2494],{},"Model, then migrate."," The biggest wins and the biggest mistakes both happen in the model file, before a single tuple moves. Spend the time getting types and rewrites right; a wrong model backfilled into millions of tuples is expensive to unwind.",[43,2497,2499],{"id":2498},"takeaways","Takeaways",[301,2501,2502,2508,2514,2520,2526],{},[304,2503,2504,2507],{},[296,2505,2506],{},"RBAC runs out of road at per-object access."," The day you need to share one resource with one person, roles have nowhere to store it. That need is the migration trigger, not a nice-to-have.",[304,2509,2510,2513],{},[296,2511,2512],{},"A role is just a relation, and a permission is just a model rule."," Assignments become tuples (data); \"which role grants what\" becomes rewrites (schema). Keeping that split clear makes the mapping mechanical.",[304,2515,2516,2519],{},[296,2517,2518],{},"Reproduce before you improve."," Model your existing roles first and prove parity, so the migration is a relocation, not a rewrite. Only then add hierarchy and per-object sharing.",[304,2521,2522,2525],{},[296,2523,2524],{},"Shadow, then cut over in order."," Run OpenFGA alongside SQL until mismatches go quiet, backfill, verify, flip, remove. Never in one commit.",[304,2527,2528,2531,2532,2534],{},[296,2529,2530],{},"Watch the three traps:"," don't port role sprawl, budget for ",[58,2533,1292],{},", and keep tuples and rows in sync with a transaction or an outbox.",[10,2536,2537],{},"RBAC is not wrong; it is a model that answers one question well and runs out when the questions change shape. ReBAC does not replace the idea of a role, it generalizes it: a role becomes one kind of relationship among many, and the migration is mostly the work of admitting that your permissions were always a graph, and finally storing them like one.",[1364,2539,2540],{},"html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}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 pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":56,"searchDepth":82,"depth":82,"links":2542},[2543,2544,2545,2546,2547,2548,2549,2550],{"id":1430,"depth":82,"text":1431},{"id":1646,"depth":82,"text":1647},{"id":1744,"depth":82,"text":1745},{"id":1949,"depth":82,"text":1950},{"id":2245,"depth":82,"text":2246},{"id":2396,"depth":82,"text":2397},{"id":2438,"depth":82,"text":2439},{"id":2498,"depth":82,"text":2499},"Roles work until someone says 'share just this document with just this person.' That is the day RBAC runs out of road. This is the practical migration: mapping role tables to relation tuples, running OpenFGA in shadow next to your SQL checks, backfilling safely, and only then unlocking the per-object sharing and hierarchy that roles never could. With the traps nobody warns you about.",{},"\u002Fsecurity\u002Fauthz\u002Frbac-to-rebac",{"title":1399,"description":2551},"rbac-to-rebac-migration-openfga","3.security\u002F2.authz\u002F2.rbac-to-rebac",[1388,1389,1390,40,1391],[1393,1394,1395],"0mJ3jvYCXMmWoLwGGZOnqlwXcDfsXzOgDSZk1zMtB1o",[2561,2564],{"title":2562,"path":2563},"Zanzibar demystifie : comment Google repond a 'cet utilisateur a-t-il le droit ?'","\u002Fsecurity\u002Fauthz\u002Fzanzibar-explained.fr",{"title":2565,"path":2566},"Mon parcours vers la certification AWS Cloud Practitioner","\u002Fcloud\u002Faws\u002Fhow-i-passed-clf-exam.fr",[2568,2575,2586],{"path":2563,"title":2562,"description":2569,"date":2570,"tags":2571,"topics":2574},"L'autorisation ressemble a un if d'une ligne, jusqu'a ce qu'on l'execute dix millions de fois par seconde sur tous les produits de Google. Zanzibar est le systeme qui a rendu cette question rapide, coherente et globale. On part du controle de permission naif pour arriver au controle d'acces base sur les relations, au modele de tuples, a la coherence avec les zookies, et aux heritiers open source comme OpenFGA que vous pouvez utiliser des aujourd'hui.","2026-07-18",[1388,1389,2572,2573,40],"Security","Distributed-Systems",[1393,1394,1395],{"path":2576,"title":2577,"description":2578,"date":2579,"tags":2580,"topics":2584},"\u002Fbackend\u002Fload-balancing\u002Fload-balancing-algorithms.fr","Algorithmes de load balancing : chacun repare le defaut du precedent","Repartir des requetes sur N serveurs sonne comme un one-liner : choisir un serveur, envoyer la requete. Puis un backend est plus lent, ou plus gros, ou tient une session, et le choix naif s'effondre. On parcourt les algorithmes classiques comme une chaine ou chacun existe pour reparer l'angle mort du precedent : round robin, pondere, least connections, power of two choices, et consistent hashing, en Go, jusqu'a la panne que tous les tutos oublient.","2026-07-20",[2581,2582,2573,2583],"Load-Balancing","Go","Resilience",[1394,2585,1395],"resilience",{"path":2587,"title":2588,"description":2589,"date":2590,"tags":2591,"topics":2593},"\u002Fbackend\u002Fapi-design\u002Fretry-storms.fr","Retry Storms : comment de bons clients font tomber des serveurs en bonne santé","Un retry semble inoffensif : la requête a échoué, on réessaie. Multipliez ça par chaque client, ajoutez une dépendance lente, et les retries deviennent un DDoS auto-infligé. On part de la boucle de retry naïve vers le backoff exponentiel, le jitter, les retry budgets et les circuit breakers, la moitié côté client de la résilience, qui complète le rate limiting côté serveur.","2026-07-11",[2583,2573,2582,2592],"Retries",[1394,1395,2585],1784570361301]