[{"data":1,"prerenderedAt":565},["ShallowReactive",2],{"blog-de-der-idp-schuetzt-den-user-auch-vor-den-sps":3,"header-blog-translations-/de/blog/der-idp-schuetzt-den-user-auch-vor-den-sps":562},{"id":4,"title":5,"author":6,"body":7,"date":546,"description":547,"draft":548,"extension":549,"image":550,"meta":551,"navigation":290,"path":552,"seo":553,"stem":554,"tags":555,"translationKey":560,"__hash__":561},"blog_de/blog/de/der-idp-schuetzt-den-user-auch-vor-den-sps.md","Der IdP schützt den User — auch vor den SPs","Patrick Hofmann",{"type":8,"value":9,"toc":537},"minimark",[10,19,22,27,34,41,48,51,58,61,65,68,86,104,126,136,139,143,153,164,167,409,422,425,436,445,460,463,467,473,479,490,494,497,507,514,517,520,533],[11,12,13,14,18],"p",{},"Ein Service Provider publiziert seine Metadata via DDISA — Logo-URL, App-Name, Redirect-URIs in einem DNS-veröffentlichten Manifest. Beim nächsten User, der sich von dort durchklickt, steht auf der Consent-Seite ",[15,16,17],"em",{},"\"Anmelden bei trusted-bank-services\""," mit einem Logo, das aussieht wie eine bekannte Bank. Es ist keine Bank. Es ist ein beliebiger SP, der den User über seine Identität täuscht. Phishing, durch die Vordertür der Consent-Seite.",[11,20,21],{},"Das war nicht hypothetisch. Die Mechanik war drin. Ich habe sie gestern rausgenommen.",[23,24,26],"h2",{"id":25},"wie-ich-es-vorher-gedacht-habe","Wie ich es vorher gedacht habe",[11,28,29,30,33],{},"Der erste Bauplan eines IdP geht implizit davon aus: ",[15,31,32],{},"SPs sind Konsumenten."," Sie publizieren ihre Metadata, sie holen sich Tokens ab, sie schicken den User durch die Auth-Flow und bekommen am Ende eine Identity zurück. Der IdP hat zwei Rollen — er authenticatet den User, und er bedient die SPs.",[11,35,36,37,40],{},"In diesem Bild ist Härtung eine Frage ",[15,38,39],{},"gegen missbräuchliche SPs",": gegen Replay-Attacks, gegen Quota-Abuse, gegen Lateral-Movement zwischen Tenants. Klassische API-Härtung. Der SP ist der potenzielle Angreifer auf den IdP-Service.",[11,42,43,44,47],{},"Bei DDISA verschärft sich das Bild. SPs registrieren sich nicht — jede Domain auf der Welt kann ihre Metadata als DNS-Manifest publizieren und damit als SP auftauchen, ohne dass der IdP davon vorher weiß. Es gibt kein Vorab-Vetting, keinen Approval-Schritt vor dem ersten Auth-Request. Das macht den Filter beim ",[15,45,46],{},"Durchreichen"," der SP-Metadata zum User nicht weniger wichtig, sondern wichtiger.",[11,49,50],{},"Das Bild ist nicht falsch. Es ist nicht vollständig.",[11,52,53,54,57],{},"Was es übersieht: der User vertraut dem IdP, nicht dem SP. Der User hat eine Beziehung zum IdP — er hat dort sein Konto, er kennt das Branding, er erwartet, dass ",[15,55,56],{},"der IdP"," ihm sagt, womit er sich gerade verbindet. Der SP ist eine Drittpartei, der gegenüber der User keine direkte Trust-Relation hat. Er kommt nur via IdP-Vermittlung in den Kontext.",[11,59,60],{},"Wenn der IdP also einfach durchreicht, was der SP an Metadata liefert, hat der SP einen Kanal, durch den er den User direkt manipulieren kann. Der IdP wird zum Megafon.",[23,62,64],{"id":63},"was-die-sp-machen-kann-wenn-man-sie-lässt","Was die SP machen kann, wenn man sie lässt",[11,66,67],{},"Das Logo ist das offensichtliche Beispiel. Es gibt mehr.",[11,69,70,78,79,81,82,85],{},[71,72,73,77],"strong",{},[74,75,76],"code",{},"javascript:","-URIs in der Metadata."," Ein SP gibt eine URI im ",[74,80,76],{},"-Schema an. Wenn der IdP diese URI als Link auf einer Auth-Flow-Seite einbaut — und sei es nur ein ",[15,83,84],{},"\"zurück zur Anwendung\"","-Button für den Fehlerfall — hat der SP XSS in der vertrauenswürdigen IdP-Domain.",[11,87,88,91,92,95,96,99,100,103],{},[71,89,90],{},"Externe Logo-URLs."," Ein SP gibt als Logo-URL ",[74,93,94],{},"https://tracker.evil.com/pixel.png"," an. Der IdP rendert das auf der Consent-Seite als ",[74,97,98],{},"\u003Cimg src>",". Jeder User, der durch die Consent-Seite geht, lädt das Pixel — der SP weiß, welche User ihn überhaupt zu sehen bekommen, bevor sie je auf ",[15,101,102],{},"\"Approve\""," geklickt haben.",[11,105,106,113,114,117,118,121,122,125],{},[71,107,108,109,112],{},"Beliebige ",[74,110,111],{},"redirect_uri","."," Ein SP publiziert ",[74,115,116],{},"https://legitimate-app.example.com/callback"," als gültigen Callback in seinem DDISA-Manifest. Im Auth-Request übergibt er aber ",[74,119,120],{},"redirect_uri=https://attacker.com/steal",". Wenn der IdP nicht gegen die ",[15,123,124],{},"publizierte"," Metadata validiert, sondern dem Request-Parameter glaubt, fließen Tokens zum Angreifer.",[11,127,128,131,132,135],{},[71,129,130],{},"Passkey-Graft."," Eine Variante auf User-Ebene: ein unauthenticated ",[74,133,134],{},"add-credential","-Endpoint erlaubt es, an ein bestehendes Konto einen weiteren Passkey ranzuhängen. Ohne harte Session-Auth davor kann ein Angreifer einen eigenen Passkey ans Konto grafen — und hat danach legitimen Dauer-Zugang.",[11,137,138],{},"Jede dieser Lücken ist plausibel. Jede ist nutzbar. Keine ist exotisch.",[23,140,142],{"id":141},"die-inversion","Die Inversion",[11,144,145,146,149,150,112],{},"Der Punkt, der beim Schließen dieser Issues klar wurde: das ist nicht ",[15,147,148],{},"Härtung gegen die SPs",". Das ist Härtung ",[15,151,152],{},"für die User gegen die SPs",[11,154,155,156,159,160,163],{},"Eine andere Trust-Boundary. Nicht zwischen IdP und SP — wo SP der potenzielle Angreifer auf den IdP-Service ist — sondern zwischen ",[15,157,158],{},"User"," und ",[15,161,162],{},"SP",", mit dem IdP als Vermittler in der Mitte. Der IdP filtert, was der SP zum User durchreicht.",[11,165,166],{},"Konkret im Code: das, was der User auf der Consent-Seite sieht, ist nicht mehr eine 1:1-Projektion der SP-Metadata.",[168,169,174],"pre",{"className":170,"code":171,"language":172,"meta":173,"style":173},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","// vorher: was die SP geliefert hat, war was der User sah\nconst consentView = {\n  appName:     sp.metadata.name,\n  logoUrl:     sp.metadata.logo,         // beliebige Quelle\n  redirectUri: req.query.redirect_uri,   // beliebige URI\n};\n\n// jetzt: nichts SP-supplied geht ungeprüft an den User\nconst consentView = {\n  appName:     sanitize(sp.metadata.name),\n  logoUrl:     null,                     // SP-supplied logos: dropped\n  redirectUri: matchRegistered(\n    req.query.redirect_uri,\n    sp.metadata.redirect_uris,           // exakter Eintrag oder Reject\n  ),\n};\n","ts","",[74,175,176,185,203,229,254,279,285,292,298,309,334,347,360,376,396,404],{"__ignoreMap":173},[177,178,181],"span",{"class":179,"line":180},"line",1,[177,182,184],{"class":183},"sHwdD","// vorher: was die SP geliefert hat, war was der User sah\n",[177,186,188,192,196,200],{"class":179,"line":187},2,[177,189,191],{"class":190},"spNyl","const",[177,193,195],{"class":194},"sTEyZ"," consentView ",[177,197,199],{"class":198},"sMK4o","=",[177,201,202],{"class":198}," {\n",[177,204,206,210,213,216,218,221,223,226],{"class":179,"line":205},3,[177,207,209],{"class":208},"swJcz","  appName",[177,211,212],{"class":198},":",[177,214,215],{"class":194},"     sp",[177,217,112],{"class":198},[177,219,220],{"class":194},"metadata",[177,222,112],{"class":198},[177,224,225],{"class":194},"name",[177,227,228],{"class":198},",\n",[177,230,232,235,237,239,241,243,245,248,251],{"class":179,"line":231},4,[177,233,234],{"class":208},"  logoUrl",[177,236,212],{"class":198},[177,238,215],{"class":194},[177,240,112],{"class":198},[177,242,220],{"class":194},[177,244,112],{"class":198},[177,246,247],{"class":194},"logo",[177,249,250],{"class":198},",",[177,252,253],{"class":183},"         // beliebige Quelle\n",[177,255,257,260,262,265,267,270,272,274,276],{"class":179,"line":256},5,[177,258,259],{"class":208},"  redirectUri",[177,261,212],{"class":198},[177,263,264],{"class":194}," req",[177,266,112],{"class":198},[177,268,269],{"class":194},"query",[177,271,112],{"class":198},[177,273,111],{"class":194},[177,275,250],{"class":198},[177,277,278],{"class":183},"   // beliebige URI\n",[177,280,282],{"class":179,"line":281},6,[177,283,284],{"class":198},"};\n",[177,286,288],{"class":179,"line":287},7,[177,289,291],{"emptyLinePlaceholder":290},true,"\n",[177,293,295],{"class":179,"line":294},8,[177,296,297],{"class":183},"// jetzt: nichts SP-supplied geht ungeprüft an den User\n",[177,299,301,303,305,307],{"class":179,"line":300},9,[177,302,191],{"class":190},[177,304,195],{"class":194},[177,306,199],{"class":198},[177,308,202],{"class":198},[177,310,312,314,316,320,323,325,327,329,332],{"class":179,"line":311},10,[177,313,209],{"class":208},[177,315,212],{"class":198},[177,317,319],{"class":318},"s2Zo4","     sanitize",[177,321,322],{"class":194},"(sp",[177,324,112],{"class":198},[177,326,220],{"class":194},[177,328,112],{"class":198},[177,330,331],{"class":194},"name)",[177,333,228],{"class":198},[177,335,337,339,341,344],{"class":179,"line":336},11,[177,338,234],{"class":208},[177,340,212],{"class":198},[177,342,343],{"class":198},"     null,",[177,345,346],{"class":183},"                     // SP-supplied logos: dropped\n",[177,348,350,352,354,357],{"class":179,"line":349},12,[177,351,259],{"class":208},[177,353,212],{"class":198},[177,355,356],{"class":318}," matchRegistered",[177,358,359],{"class":194},"(\n",[177,361,363,366,368,370,372,374],{"class":179,"line":362},13,[177,364,365],{"class":194},"    req",[177,367,112],{"class":198},[177,369,269],{"class":194},[177,371,112],{"class":198},[177,373,111],{"class":194},[177,375,228],{"class":198},[177,377,379,382,384,386,388,391,393],{"class":179,"line":378},14,[177,380,381],{"class":194},"    sp",[177,383,112],{"class":198},[177,385,220],{"class":194},[177,387,112],{"class":198},[177,389,390],{"class":194},"redirect_uris",[177,392,250],{"class":198},[177,394,395],{"class":183},"           // exakter Eintrag oder Reject\n",[177,397,399,402],{"class":179,"line":398},15,[177,400,401],{"class":194},"  )",[177,403,228],{"class":198},[177,405,407],{"class":179,"line":406},16,[177,408,284],{"class":198},[11,410,411,412,415,416,418,419,421],{},"Logos werden komplett gedroppt — es gibt im Moment keine kuratierte Allowlist, also gibt es kein Logo. URIs müssen ",[74,413,414],{},"https://","-Schema haben, alles andere wird beim Metadata-Ingest abgelehnt. ",[74,417,111],{}," aus dem Auth-Request muss exakt einem in der publizierten Metadata gelisteten Eintrag entsprechen. ",[74,420,134],{}," läuft nur mit existierender authentifizierter Session.",[11,423,424],{},"Der SP ist nicht der Kunde des IdP. Der User ist der Kunde des IdP. Der SP ist eine Drittpartei, der gegenüber der IdP eine Schutzpflicht hat — gegenüber dem User.",[23,426,428,429,432,433],{"id":427},"default-consent-statt-open","Default ",[74,430,431],{},"consent"," statt ",[74,434,435],{},"open",[11,437,438,439,441,442,444],{},"Die sichtbare Konsequenz ist eine Default-Änderung: die Policy für neue SP-Anbindungen ist nicht mehr ",[74,440,435],{}," (jeder SP, der die Metadata-Dance durchläuft, kann sofort User authenticaten), sondern ",[74,443,431],{}," (jeder neue SP muss explizit vom User-Owner zugelassen werden, bevor er User-Sessions kriegt).",[11,446,447,449,450,453,454,456,457,112],{},[74,448,435],{}," war der alte Default, weil ich an SPs als Konsumenten gedacht habe — ",[15,451,452],{},"je geringer die Reibung beim Anbinden, desto besser",". ",[74,455,431],{}," ist der neue Default, weil ich an SPs als potenzielle Angreifer auf User gedacht habe — ",[15,458,459],{},"je expliziter die Zulassung, desto kontrollierter der Trust-Layer",[11,461,462],{},"Das ist nicht eine kleine Konfig-Änderung. Es ist eine Aussage darüber, wem der IdP gehört.",[23,464,466],{"id":465},"eine-richtung","Eine Richtung",[11,468,469,470,472],{},"Logos gedroppt, URIs validiert, ",[74,471,111],{}," gegen die Metadata gemappt, Passkey-Graft geschlossen, ein Hardening-Batch, Default geflippt. Keine davon ist ein dramatisches Architektur-Refactoring — jede einzelne ist ein paar Zeilen Code, ein zusätzlicher Filter, ein gestrichener Pfad.",[11,474,475,476],{},"Was sie zusammenhält, ist das Bild dahinter: ",[15,477,478],{},"welche Partei ist die schutzwürdige?",[11,480,481,482,485,486,489],{},"Wenn diese Frage falsch beantwortet ist, baut man die Filter an der falschen Stelle. Man härtet gegen Replay (",[15,483,484],{},"die SP nervt mich",") und vergisst das Logo (",[15,487,488],{},"die SP belügt meine User","). Beides sind reale Angriffe, aber sie haben unterschiedliche Opfer.",[23,491,493],{"id":492},"wer-ist-der-kunde","Wer ist der Kunde",[11,495,496],{},"Ein IdP fühlt sich anfangs an wie ein Service für SPs. Sie sind die, die die API benutzen, OAuth-Flows triggern, Tokens abholen. Die, die Dokumentation lesen und Tickets aufmachen. Sie sind sichtbar.",[11,498,499,500,502,503,506],{},"Der User taucht in diesem Bild kaum auf. Er klickt auf der Consent-Seite ",[15,501,102],{}," und ist weg. Er sieht das Logo, sieht den App-Namen, sieht eine Permissions-Liste — das war's. Er ist ",[15,504,505],{},"user agent",", nicht Kunde.",[11,508,509,510,513],{},"Aber er ist der einzige Akteur, der wirklich verloren hat, wenn etwas schiefläuft. Der SP, der ein Logo missbraucht hat, hat im Worst Case seinen Account gesperrt bekommen. Der User, der auf das Logo reingefallen ist, hat im Worst Case seine Identity verloren — die er nicht aussperren kann, weil sie ",[15,511,512],{},"seine"," ist.",[11,515,516],{},"Der IdP ist nicht der Diener der SPs. Er ist der Vermittler zwischen User und SP. Und in dieser Mittlerrolle gehört seine Loyalität zu der Partei, die im Schadensfall nicht zurück kann.",[518,519],"hr",{},[11,521,522],{},[15,523,524,525,532],{},"Code: ",[526,527,531],"a",{"href":528,"rel":529},"https://github.com/openape-ai/openape",[530],"nofollow","github.com/openape-ai/openape",", MIT-lizenziert.",[534,535,536],"style",{},"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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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);}",{"title":173,"searchDepth":187,"depth":187,"links":538},[539,540,541,542,544,545],{"id":25,"depth":187,"text":26},{"id":63,"depth":187,"text":64},{"id":141,"depth":187,"text":142},{"id":427,"depth":187,"text":543},"Default consent statt open",{"id":465,"depth":187,"text":466},{"id":492,"depth":187,"text":493},"2026-05-05","Ein Service Provider lädt sein Logo hoch und kann damit auf der Consent-Seite den User über die Identität täuschen. Eine Notiz aus einem Hardening-Sprint, in dem ich erkannt habe, dass der schutzwürdige Akteur nicht die SP ist.",false,"md",null,{},"/blog/de/der-idp-schuetzt-den-user-auch-vor-den-sps",{"title":5,"description":547},"blog/de/der-idp-schuetzt-den-user-auch-vor-den-sps",[556,557,558,559],"OpenApe","Identity","Security","Building in Public","idp-protects-user-from-sps","hIGyzDiq4Gpma-UAQd_EYeYSXl0g3AZW_2vz2M7SMbA",{"de":563,"en":564},"/de/blog/der-idp-schuetzt-den-user-auch-vor-den-sps","/en/blog/idp-protects-user-from-sps",1779001885457]