Architektur zum Zusammenführen mehrerer Benutzerkonten


176

Okay, ich habe eine Website, auf der Sie sich registrieren und anmelden können. Sie können sich auch mit Ihrem Facebook-, Twitter- oder Linkedin-Konto anmelden.

Es ist wichtig, dass Benutzer nur ein Konto registriert haben. Irgendwie möchte ich die Konten von Benutzern zusammenführen, wenn diese unterschiedliche Methoden zum Anmelden verwenden. Was ist die beste Lösung, um dies zu lösen?

Beispielsweise meldet sich der Benutzer mit seinem Facebook-Konto an. Ich benutze die Daten, um automatisch ein Konto für ihn zu registrieren. Soll ich eine E-Mail mit einem Benutzernamen und einem Passwort unserer Website senden? (Wenn dies mit der Politik von Facebook in Ordnung ist). Soll ich ihnen einen zweiten Bildschirm geben, auf dem sie einen Benutzernamen und ein Passwort eingeben können? Aber das ist nicht die Idee, sich mit Ihrem Facebook-Konto anzumelden. Es sollte Ihr Verfahren zur Teilnahme vereinfachen.

Es ist auch möglich, dass sich der Benutzer auf unserer Website registriert hat und sich das nächste Mal mit seinem Twitter-Konto anmeldet. Wie kann ich diese beiden Konten zu einem zusammenführen? Was ist der beste Weg?

Meine Frage lautet also im Grunde: Ich habe 4 verschiedene Möglichkeiten, wie ein Benutzer Mitglied unserer Website wird. Wie kann ich sicherstellen, dass alle diese vier Möglichkeiten nur ein Konto erstellen, wenn ein Benutzer mehrere Möglichkeiten verwendet? Was ist der beste Ablauf, um sicherzustellen, dass der Benutzer selbst keinen Ärger bekommt?


Bearbeiten:

3 Jahre nachdem ich diese Frage gestellt habe, gebe ich die Antwort selbst in einer Reihe von Artikeln: https://www.peternijssen.nl/social-network-authentication-setup/
https://www.peternijssen.nl/social- Netzwerkauthentifizierung-Google /
https://www.peternijssen.nl/social-network-authentication-merging-accounts/
https://www.peternijssen.nl/social-network-authentication-twitter-facebook/


7
Natürlich ist es am besten zu versuchen, zu verhindern, dass ein Benutzer überhaupt mehrere Konten hat, indem Sie mehrere Anmeldemethoden wie Stapelüberlauf zulassen, aber selbst SO bietet Moderatoren die Möglichkeit, Konten zusammenzuführen. Ich biete Kopfgeld für jeden an, der eine Architektur beschreiben kann, um dies zu ermöglichen. Es muss eine bessere Lösung geben als "Update Post Set UserID = 2 wobei UserID = 1"
David Boike

E-Mail und Telefon können als Zusammenführungsschlüssel verwendet werden, andere?
Wuaner

Hallo, der von Ihnen angegebene Link scheint nicht zu funktionieren. Es wird nur auf die Homepage der Website
verwiesen

Die Links wurden aktualisiert. Artikel sind allerdings 6 Jahre alt.
PT

Antworten:


120

Ich stehe im Moment vor genau der gleichen Aufgabe. Das Design, das ich ausgearbeitet habe, ist ziemlich einfach, aber es funktioniert gut.

Die Kernidee besteht darin, dass Modelle für eine lokale Site-Identität und die Site-Identitäten von Drittanbietern isoliert bleiben, aber später verknüpft werden. Jeder Benutzer, der sich auf der Site anmeldet, verfügt über eine lokale Identität, die einer beliebigen Anzahl von Site-Identitäten von Drittanbietern zugeordnet ist.

Ein lokaler Identitätsdatensatz enthält ein Minimum an Informationen - es kann sich sogar um ein einzelnes Feld handeln - nur einen Primärschlüssel. (Bei meiner Bewerbung ist mir die E-Mail-Adresse, der Name oder das Geburtsdatum des Benutzers egal. Ich möchte nur wissen, dass es sich um die Person handelt, die sich die ganze Zeit in diesem Konto angemeldet hat.)

Die Identitäten von Drittanbietern enthalten Informationen, die nur für die Authentifizierung bei einem Drittanbieter relevant sind. Für OAuth bedeutet dies normalerweise eine Benutzerkennung (wie eine ID, eine E-Mail oder ein Benutzername) und eine Dienstkennung (die angibt, mit welcher Site oder welchem ​​Dienst authentifiziert wurde). In anderen Teilen der Anwendung außerhalb der Datenbank wird diese Dienstkennung mit einer Methode zum Abrufen der relevanten Benutzerkennung von diesem Dienst gepaart, und auf diese Weise wird die Authentifizierung durchgeführt. Für OpenID verwenden wir denselben Ansatz, außer dass die Authentifizierungsmethode allgemeiner ist (da wir fast immer genau dasselbe Protokoll ausführen können - außer dass wir eine andere Identitäts-URL verwenden und dies unsere Service-ID ist).

Schließlich führe ich Aufzeichnungen darüber, welche Identitäten von Drittanbietern mit welcher lokalen Identität gepaart sind. Um diese Datensätze zu generieren, sieht der Ablauf folgendermaßen aus:

  • Ein Benutzer meldet sich zum ersten Mal mit einer Identität eines Drittanbieters an. Ein lokaler Identitätsdatensatz wird erstellt, dann ein Identitätsdatensatz eines Drittanbieters, und dann werden sie gepaart.
  • In einem Control Panel wird dem Benutzer die Möglichkeit geboten, ein Konto zu verknüpfen, indem er sich bei Diensten von Drittanbietern anmeldet. (Ziemlich einfach, wie das funktioniert.)
  • In dem Szenario, in dem der Benutzer unabsichtlich mehrere Konten erstellt, ist die Lösung ziemlich einfach. Während der Benutzer bei einem der Konten angemeldet ist, meldet er sich bei einem anderen Konto an, mit dem er sich zuvor bei der Site angemeldet hat (über die obige Funktion des Bedienfelds). Der Webdienst erkennt diese Kollision (die lokale Identität des angemeldeten Benutzers unterscheidet sich von der lokalen Identität, die mit der gerade angemeldeten Identität eines Drittanbieters verknüpft ist), und der Benutzer wird zu einer Zusammenführung des Kontos aufgefordert.

Beim Zusammenführen von Konten müssen Sie jedes einzelne Feld der lokalen Identität zusammenführen (was von Anwendung zu Anwendung unterschiedlich ist und einfach sein sollte, wenn Ihre lokalen Identitätsdatensätze nur einige Felder enthalten) und anschließend die verknüpften Identitäten von Drittanbietern sicherstellen sind mit der resultierenden lokalen Identität verknüpft.


1
Dies ist eine sehr gute Lösung (ich liebe die Idee, die lokale Identitätskollision automatisch zu erkennen)! Ich frage mich, ob Sie eine Möglichkeit gefunden haben, den Benutzer automatisch bei den "zusätzlichen" Konten anzumelden, die nach der ersten Anmeldung bei der Anwendung verknüpft sind. Müsste sich der Benutzer bei jedem Besuch separat bei jedem Konto anmelden (wenn noch keine aktive Sitzung mit diesem Anbieter vorhanden ist)?
Alexandra

@Alexandra Was meinst du mit "extra Accounts"? Fragen Sie sich, was passiert, wenn sich der Benutzer über mehrere verschiedene Authentifikatoren bei der Anwendung anmeldet und mit jedem eine neue lokale Identität erstellt?
Cheeken

Ich denke, dies erfordert eine angemessene Klärung und eine eigene Frage: stackoverflow.com/questions/11060368/…
Alexandra

3
Frage, aber was ist, wenn sich der Benutzer mit derselben E-Mail-Adresse auf der Website registriert? fordern wir sie auf, dass bereits eine E-Mail vorhanden ist (die E-Mail, die vom Drittanbieter-Modell stammt), oder registrieren wir sie weiterhin auf der Website und haben diese Konten dann zusammengeführt?
user962206

@ user962206 Viele Dienste geben Ihnen aus Datenschutzgründen ohnehin nicht die "echte" E-Mail-Adresse. Zum Beispiel wird Twitter Ihnen keine E-Mail-Adresse geben, soweit ich weiß, wird Facebook "user.name@facebook.com" geben, was ich bezweifle, dass jemand für die Registrierung verwenden würde.
Kapex

44

Ich neige dazu, viele Websites zu finden, die basierend auf E-Mail als überlappendem Verbindungsfaktor zusammengeführt werden.

Ich kann sehen, dass dies eine praktikable Option ist, aber auch hier hängt es von Ihrer Präferenz ab, wie Sie zusammenführen möchten. Die E-Mail-Adresse ist die Hauptmethode, mit der Benutzer wichtige Informationen auf Ihrer Website überprüfen, z. B. das Ändern Ihres Passworts, die Beendigung des Dienstes, den niedrigen Kontostand usw. Es ist fast wie das Sozialversicherungsnummernsystem des Webs, jedoch mit der Fähigkeit zur Kommunikation . Kulturell: Ich denke, es ist vernünftig anzunehmen, dass eine E-Mail eine ziemlich eindeutige Identität für alle OAuth-Authentifizierungsdienste ist. Zugegeben, es ist das, was die Anmeldeformulare für Facebook und Google verlangen.

Mein aktueller Denkprozess.

Die Anmeldeseite bietet 3 Optionen

  • Die Mitgliedschaft Ihrer eigenen Site
  • Mit Facebook einloggen
  • Loggen Sie sich mit Google ein

1) Benutzeranmeldungen zum ersten Mal: ​​Lösen Sie einen Registrierungsablauf aus, in dem ein Konto erstellt und zum ersten Mal ausgefüllt wird.

 if the user logins using Facebook (or whatever 3rd party login)
      1) call the Facebook api asking for their information (email, name, etc...) 
      2) create an account membership entry in your database somewhat like this 

         Table = Users
         [ UserId   |       Email             | Password ]
         [    23     | "newuser@coolmail.com" |  *null*  ]

      3) create an external auths entry like so
         *ProviderUserId is the unique id of that user on the provider's site

         Table = ExternalAuths
         [ ExternalAuthId  |  User_UserId   | ProviderName |   ProviderUserId  ]
         [    56           |      23        |   Facebook   |  "max.alexander.9"]

 if the user wants to create an account with your own registration it would just be this           

         Table = Users
         [ UserId   |       Email           |   Password  ]
         [    23     | newuser@coolmail.com |  myCoolPwd  ]

2) Zu einem anderen Zeitpunkt kommt der Nutzer zurück, beschließt jedoch, auf das Google-Login zu klicken

      1) call the Google api asking for their information (email, name, etc...) 

      2) once you get the email, match it up to the userId entry with the existing email 

      3) create an additional External auth entry as such

         Table = ExternalAuths
         [ ExternalAuthId  |  User_UserId   | ProviderName |   ProviderUserId  ]
         [    56           |      23        |   Facebook   |  "max.alexander.9"]
         [    57           |      23        |    Google    |  "1234854368"     ]

3) Jetzt haben Sie das Konto zusammengeführt, dem Sie vertrauen, dass die E-Mail in Ihren Datenbankeinträgen mit denen übereinstimmt, denen Sie bei den externen Anmeldungen vertrauen.

Also für nachfolgende Anmeldungen

Was ist, wenn Sie zuerst externe Anmeldungen haben und dann möchten, dass sich ein Benutzer später mit einem Kennwort anmelden kann?

Ich sehe zwei einfache Möglichkeiten, dies zu tun

  • Fragen Sie bei jeder ersten Anmeldung, wenn ein Konto über eine externe Authentifizierung erstellt wird, nach einem Kennwort, um den ersten Eintrag in Ihre Anwendung abzuschließen

  • Wenn sie sich bereits zuerst über Facebook oder Google registriert haben, wollten sie sich irgendwie über das Registrierungsformular Ihrer eigenen Website registrieren. Ermitteln Sie, ob die von ihnen eingegebene E-Mail-Adresse bereits vorhanden ist, fragen Sie sie nach einem Passwort und senden Sie ihnen nach Abschluss der Registrierung eine E-Mail-Bestätigung.


1
Wenn Sie ein externes Authentifizierungsschema (Facebook, Google, Twitter usw.) verwenden, haben Sie niemals Zugriff auf das Kennwort des externen Anbieters. Das Passwort im obigen Beispiel ist das Passwort für Ihre EIGENE Web-App.
Max Alexander

6
Twitter bietet den Nutzern keine E-Mail an. Wenn die Twitter-Benutzer-ID nach der ersten Authentifizierung nicht vorhanden ist, fordern Sie ihn zur Eingabe einer E-Mail-Adresse und eines Kennworts auf. Wenn die E-Mail-Adresse und das Passwort vorhanden sind, verknüpfen Sie die Konten. Wenn dies nicht der Fall ist, speichern Sie die E-Mail-Adresse und das Kennwort für nahtlose nachfolgende Authentifizierungen. War das überhaupt hilfreich?
Max Alexander

1
Nebenbei bemerkt ist es jetzt möglich, Berechtigungen für Ihre Twitter-App anzufordern , um die E-Mail-Adresse des Benutzers abzurufen.
Sunil D.

2
Lässt Facebook Leute ihre E-Mails verifizieren? Ich weiß, dass Github es zum Beispiel nicht tut. Ein böswilliger Benutzer könnte sich mit dem E-Mail-Namen eines anderen bei Github anmelden und dann mit dieser Strategie Zugriff auf dessen Konto auf Ihrer Website erhalten. (Githubs API sagt Ihnen, ob ihre E-Mail verifiziert ist oder nicht - Sie können jeden ablehnen, der sich bei Github unter einer nicht verifizierten E-Mail-Adresse authentifiziert)
Matthew Moisen

6
Dies ist eine Sicherheitsverletzung, wenn sich ein Benutzer über soziale Medien anmeldet, seine E-Mail-Adresse in sozialen Medien bearbeitet, eine andere Person dieselbe E-Mail-Adresse verwendet, sich in sozialen Medien registriert und sich dann anmeldet und mit einem anderen Konto zusammengeführt wird. Dies ist sehr unwahrscheinlich, aber dennoch ein Verstoß. Oder fehlt mir etwas?
Shane

35

Ich habe das mit sled.com durchgemacht. Hier gibt es mehrere Probleme beim Erstellen von Konten und beim Unterstützen mehrerer Konten von Drittanbietern für die Anmeldung. Einige von ihnen sind:

  • Müssen Sie sowohl ein lokales Passwort als auch Anmeldungen von Drittanbietern unterstützen?

Für sled.com habe ich beschlossen, das lokale Passwort aufgrund des geringen Mehrwerts und der zusätzlichen Kosten für die Sicherung eines Passworteingabeformulars zu löschen. Es gibt viele bekannte Angriffe zum Brechen von Passwörtern. Wenn Sie Passwörter einführen möchten, müssen Sie sicherstellen, dass diese nicht leicht zu knacken sind. Sie müssen sie auch in einem One-Way-Hash oder ähnlichem speichern, um zu verhindern, dass sie auslaufen.

  • Wie viel Flexibilität möchten Sie bei der Unterstützung mehrerer Konten von Drittanbietern einräumen?

Anscheinend haben Sie bereits die drei Anmeldeanbieter ausgewählt: Facebook, Twitter und LinkedIn. Das ist großartig, weil Sie OAuth verwenden und mit einer genau definierten Gruppe vertrauenswürdiger Anbieter zusammenarbeiten. Ich bin kein Fan von OpenID. Die verbleibende Frage ist, ob Sie mehrere Drittanbieter-Konten desselben Anbieters unterstützen müssen (z. B. ein lokales Konto mit zwei verknüpften Twitter-Konten). Ich gehe davon aus, dass dies nicht der Fall ist, aber wenn Sie dies tun, müssen Sie dies in Ihrem Datenmodell berücksichtigen.

Für Sled unterstützen wir die Anmeldung mit Facebook, Twitter und Yahoo! und in jedem Benutzerkonto einen Schlüssel für jeden speichern: {"_id": "djdjd99dj", "yahoo": "dj39djdj", twitter: "3723828732", "facebook": "12837287"}. Wir richten eine Reihe von Einschränkungen ein, um sicherzustellen, dass jedes Konto eines Drittanbieters nur mit einem einzelnen lokalen Konto verknüpft werden kann.

Wenn Sie mehrere Konten von demselben Drittanbieter zulassen möchten, müssen Sie Listen oder andere Strukturen verwenden, um dies zu unterstützen, und damit alle anderen Einschränkungen, um die Eindeutigkeit sicherzustellen.

  • Wie verbinde ich mehrere Konten?

Wenn sich der Benutzer zum ersten Mal für Ihren Dienst anmeldet, geht er zuerst zum Drittanbieter und gibt eine verifizierte Drittanbieter-ID zurück. Anschließend erstellen Sie ein lokales Konto für sie und sammeln alle anderen gewünschten Informationen. Wir erfassen ihre E-Mail-Adresse und bitten sie, einen lokalen Benutzernamen auszuwählen (wir versuchen, das Formular mit dem vorhandenen Benutzernamen des anderen Anbieters vorab auszufüllen). Eine spätere lokale Kennung (E-Mail, Benutzername) ist für die spätere Wiederherstellung des Kontos sehr wichtig.

Der Server weiß, dass dies eine erstmalige Anmeldung ist, wenn der Browser kein Sitzungscookie (gültig oder abgelaufen) für ein vorhandenes Konto hat und das verwendete Drittanbieter-Konto nicht gefunden wird. Wir versuchen, den Benutzer darüber zu informieren, dass er sich nicht nur anmeldet, sondern ein neues Konto erstellt, sodass er hoffentlich pausiert und sich stattdessen mit seinem vorhandenen Konto anmeldet, wenn er bereits ein Konto hat.

Wir verwenden genau denselben Ablauf, um zusätzliche Konten zu verknüpfen. Wenn der Benutzer jedoch von einem Drittanbieter zurückkommt, wird das Vorhandensein eines gültigen Sitzungscookies verwendet, um zwischen dem Versuch, ein neues Konto mit einer Anmeldeaktion zu verknüpfen, zu unterscheiden. Wir erlauben nur ein Drittanbieter-Konto für jeden Typ. Wenn bereits ein Konto verknüpft ist, blockieren Sie die Aktion. Dies sollte kein Problem sein, da die Schnittstelle zum Verknüpfen eines neuen Kontos deaktiviert ist, wenn Sie bereits eines haben (pro Anbieter), aber nur für den Fall.

  • Wie füge ich Konten zusammen?

Wenn ein Benutzer versucht hat, ein neues Drittanbieter-Konto zu verknüpfen, das bereits mit einem lokalen Konto verknüpft ist, fordern Sie ihn einfach auf, zu bestätigen, dass er die beiden Konten zusammenführen möchte (vorausgesetzt, Sie können eine solche Zusammenführung mit Ihrem Datensatz durchführen - oft einfacher gesagt als getan). Sie können ihnen auch eine spezielle Schaltfläche zum Anfordern einer Zusammenführung bereitstellen. In der Praxis müssen sie jedoch nur ein anderes Konto verknüpfen.

Dies ist eine ziemlich einfache Zustandsmaschine. Der Benutzer kommt mit einer Konto-ID eines Drittanbieters vom Drittanbieter zurück. Ihre Datenbank kann sich in einem von drei Zuständen befinden:

  1. Das Konto ist mit einem lokalen Konto verknüpft und es ist kein Sitzungscookie vorhanden -> Anmelden
  2. Das Konto ist mit einem lokalen Konto verknüpft und ein Sitzungscookie ist vorhanden -> Zusammenführen
  3. Das Konto ist nicht mit einem lokalen Konto verknüpft und es ist kein Sitzungscookie vorhanden -> Anmelden
  4. Das Konto ist nicht mit einem lokalen Konto verknüpft und es ist ein Sitzungscookie vorhanden -> Zusätzliches Konto verknüpfen

    • Wie führe ich eine Kontowiederherstellung bei Drittanbietern durch?

Dies ist immer noch experimentelles Gebiet. Ich habe kein perfektes UX dafür gesehen, da die meisten Dienste neben den Konten von Drittanbietern sowohl ein lokales Kennwort bereitstellen als auch sich auf den Anwendungsfall "Passwort vergessen" konzentrieren und nicht auf alles andere, was schief gehen kann.

Bei Sled haben wir uns für "Benötigen Sie Hilfe beim Anmelden?" Wenn Sie auf klicken, fragen Sie den Benutzer nach seiner E-Mail-Adresse oder seinem Benutzernamen. Wir suchen nach und wenn wir ein passendes Konto finden, senden Sie diesem Benutzer eine E-Mail mit einem Link, über den er sich automatisch beim Dienst anmelden kann (einmalig gültig). Sobald sie eingegangen sind, führen wir sie direkt zur Seite zum Verknüpfen von Konten, teilen ihnen mit, dass sie einen Blick darauf werfen und möglicherweise zusätzliche Konten verknüpfen sollen, und zeigen ihnen die Konten von Drittanbietern, die sie bereits verknüpft haben.


Diese Lösung ist viel besser für mich, danke!
Roy Shoa

2
Sitzungscookie ist nicht gut genug, um den Benutzer zu identifizieren. Was ist, wenn ein anderer Benutzer dasselbe Gerät verwendet?
DeepBlue

23

Beide Ansätze für das automatische Zusammenführen von Konten hinterlassen eine ziemlich große Sicherheitslücke, die es jemandem ermöglichen würde, ein Konto zu übernehmen. Beide scheinen davon auszugehen, dass der Benutzer der ist, für den sie sich ausgeben, wenn sie einem registrierenden Benutzer die Zusammenführungsoption anbieten.

Meine Empfehlung zur Minderung der Sicherheitsanfälligkeit besteht darin, den Benutzer vor der Zusammenführung zur Authentifizierung bei einem der bekannten Identitätsanbieter anzufordern, um die Identität des Benutzers zu überprüfen.

Beispiel: Benutzer A registriert sich mit Facebook-Identität. Einige Zeit später kehren sie zu Ihrer Site zurück und versuchen, mit Windows Live ID darauf zuzugreifen und den Registrierungsprozess zu starten. Ihre Website fordert Benutzer A auf mit ... Es sieht so aus, als hätten Sie sich zuvor bei Facebook registriert. Bitte melden Sie sich mit Facebook an (Link angeben) und wir können Ihre Windows Live ID mit Ihrem vorhandenen Profil zusammenführen.

Eine andere Alternative besteht darin, ein gemeinsames Geheimnis (Passwort / persönliche Frage) bei der Erstregistrierung zu speichern, das der Benutzer beim Zusammenführen von Identitäten angeben muss. Dies bringt Sie jedoch wieder in das Geschäft des Speicherns gemeinsamer Geheimnisse. Dies bedeutet auch, dass Sie das Szenario behandeln müssen, in dem sich der Benutzer nicht an das gemeinsame Geheimnis und den damit verbundenen Workflow erinnert.


Was tun Sie, wenn Sie keine E-Mail-Adresse vom Anbieter erhalten?
Fred.kassi

1

Die meisten Beiträge sind ziemlich alt und ich denke, Googles kostenloser Firebase-Authentifizierungsdienst gab es noch nicht. Nach der Überprüfung mit OAuth übergeben Sie das OAuth-Token an es und erhalten eine eindeutige Benutzer-ID, die Sie als Referenz speichern können. Unterstützte Anbieter sind Google, Facebook, Twitter, GitHub und es besteht die Möglichkeit, benutzerdefinierte und anonyme Anbieter zu registrieren.


Es gibt Anwendungsfälle, bei denen Firebase keinen Sinn ergibt. Zum Beispiel Intranetsysteme, die mehr als eine Anmeldung haben.
Bostwick


-4

Sie sollten die Anmeldung von einem Konto aus zulassen. Wenn Sie angemeldet sind, können Sie ein anderes Konto hinzufügen, um es zusammenzuführen.


4
Und was passiert, wenn der Benutzer dies nicht tut und sich mit 4 verschiedenen Konten befindet? Wie erstellen Sie eine Architektur, die in diesem Fall eine Zusammenführung ermöglicht?
David Boike

Abhängig von Ihren Anforderungen kann dies tatsächlich ein gültiger Vorschlag sein. Ich möchte nur diejenigen warnen, die glauben, dass das Zusammenführen oder Verknüpfen von Konten später nachträglich erfolgen könnte: Wenn Sie glauben, dass dies später gewünscht wird, sollten Sie sich am Anfang mit Ihrem Datenbankdesign darauf vorbereiten: Eins Option ist eine UserGroup und eine UserMapping. Sie können eine OAuth-Benutzer-ID oder eine E-Mail- und Kennwort-Benutzer-ID einer Benutzergruppe zuordnen.
BumbleB2na
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.