Was ist der richtige OAuth 2.0-Flow für eine mobile App?


86

Ich versuche, die delegierte Autorisierung in einer Web-API für mobile Apps mithilfe von OAuth 2.0 zu implementieren. Gemäß der Spezifikation unterstützt der implizite Grant-Flow keine Aktualisierungstoken. Sobald ein Zugriffstoken für einen bestimmten Zeitraum gewährt wurde, muss der Benutzer der App erneut Berechtigungen erteilen, sobald das Token abläuft oder widerrufen wird.

Ich denke, dies ist ein gutes Szenario für Javascript-Code, der in einem Browser ausgeführt wird, wie in der Spezifikation erwähnt. Ich versuche, die Zeiten zu minimieren, in denen der Benutzer der App Berechtigungen erteilen muss, um ein Token zu erhalten. Daher scheint der Ablauf des Autorisierungscodes eine gute Option zu sein, da er Aktualisierungstoken unterstützt.

Dieser Ablauf scheint jedoch stark von einem Webbrowser abhängig zu sein, um die Umleitungen durchzuführen. Ich frage mich, ob dieser Ablauf immer noch eine gute Option für eine mobile App ist, wenn ein eingebetteter Webbrowser verwendet wird. Oder sollte ich mit dem impliziten Fluss gehen?


1
Die Frage wäre: Ist es die höchste Priorität, dass der Benutzer nach dem ersten Login nie wieder ein Passwort eingeben muss?
wenigsten Privileg

Ja, das ist genau meine Anforderung. Der Benutzer sollte das Passwort nur einmal eingeben. Ich möchte jedoch kein Token mit unendlicher Lebensdauer einrichten und in der mobilen App behalten, da dies gegen die Möglichkeit des Widerrufs des Tokens verstoßen würde. (Es sei denn, ich füge der mobilen App eine Logik hinzu, um festzustellen, dass die Anforderung nicht autorisiert war, und
fordere danach

1
Sie können ein Token mit unendlicher Lebensdauer hinzufügen und es trotzdem widerrufen. Und ja, die App-Logik sollte dies erkennen können. RFC 6750 definiert eine Möglichkeit, um zu überprüfen, ob der Fehler auf ein widerrufenes Token zurückzuführen ist.
Pedro Felix

1
Bitte vermeiden Sie Webansichten (es sei denn, Sie besitzen den vollständigen Stapel und verwenden kein soziales Login), die die Möglichkeit einer Kompromittierung von Passwörtern eröffnen. Wenn ich von einem eingebetteten Benutzeragenten eines Drittanbieters nach Anmeldeinformationen gefragt werde, deinstalliere ich die App. Einige APIs verbieten jetzt sogar solche Integrationen wie diese dev.fitbit.com/docs/oauth2. Ich habe eine andere Antwort gegeben, um einige dieser Konzepte weiter zu klären ( stackoverflow.com/a/38582630/752167 )
Matt C

Antworten:


89

Erläuterung: Mobile App = Native App

Wie in anderen Kommentaren und einigen Online-Quellen angegeben, scheint implizit eine natürliche Passform für mobile Apps zu sein. Die beste Lösung ist jedoch nicht immer eindeutig (und implizit wird aus den unten diskutierten Gründen nicht empfohlen).

Best Practices für native App OAuth2

Unabhängig davon, für welchen Ansatz Sie sich entscheiden (es sind einige Kompromisse zu berücksichtigen), sollten Sie die hier beschriebenen Best Practices für native Apps mit OAuth2 beachten: https://tools.ietf.org/html/rfc8252

Betrachten Sie die folgenden Optionen

Implizit

Soll ich implizit verwenden?

Zitat aus Abschnitt 8.2 https://tools.ietf.org/html/rfc8252#section-8.2

Der implizite Berechtigungsfluss für die Gewährung von OAuth 2.0 (definiert in Abschnitt 4.2 von OAuth 2.0 [RFC6749]) funktioniert im Allgemeinen mit der Praxis, die Autorisierungsanforderung im Browser auszuführen und die Autorisierungsantwort über eine URI-basierte Kommunikation zwischen Apps zu empfangen.
Da der implizite Fluss jedoch nicht durch PKCE [RFC7636] geschützt werden kann (was in Abschnitt 8.1 erforderlich ist), wird die Verwendung des impliziten Flusses mit nativen Apps NICHT EMPFOHLEN .

Über den impliziten Datenfluss gewährte Zugriffstoken können auch nicht ohne Benutzerinteraktion aktualisiert werden. Daher ist der Berechtigungscode-Erteilungsfluss, der Aktualisierungstoken ausgeben kann, die praktischere Option für native App-Berechtigungen, bei denen Zugriffstoken aktualisiert werden müssen.

Autorisierungscode

Wenn Sie sich für den Autorisierungscode entscheiden, besteht ein Ansatz darin, über Ihre eigene Webserverkomponente einen Proxy zu erstellen, der die Tokenanforderungen mit dem Clientgeheimnis angereichert, um zu vermeiden, dass sie in der verteilten App auf Geräten gespeichert werden.

Auszug unten aus: https://dev.fitbit.com/docs/oauth2/

Der Authorization Code Grant-Ablauf wird für Anwendungen empfohlen, die über einen Webdienst verfügen. Dieser Ablauf erfordert eine Server-zu-Server-Kommunikation unter Verwendung des Client-Geheimnisses einer Anwendung.

Hinweis: Legen Sie Ihr Client-Geheimnis niemals in verteilten Code, z. B. Apps, die über einen App Store oder clientseitiges JavaScript heruntergeladen wurden.

Anwendungen, die keinen Webdienst haben, sollten den impliziten Grant-Flow verwenden.

Fazit

Die endgültige Entscheidung sollte Ihre gewünschte Benutzererfahrung, aber auch Ihre Risikobereitschaft berücksichtigen, nachdem Sie eine ordnungsgemäße Risikobewertung Ihrer ausgewählten Ansätze vorgenommen und die Auswirkungen besser verstanden haben.

Eine gute Lektüre finden Sie hier https://auth0.com/blog/oauth-2-best-practices-for-native-apps/

Eine andere ist https://www.oauth.com/oauth2-servers/oauth-native-apps/, die besagt

Die derzeitige Best Practice der Branche besteht darin, den Autorisierungsablauf zu verwenden, während das Clientgeheimnis weggelassen wird, und einen externen Benutzeragenten zu verwenden, um den Ablauf abzuschließen. Ein externer Benutzeragent ist normalerweise der native Browser des Geräts (mit einer von der nativen App getrennten Sicherheitsdomäne), sodass die App nicht auf den Cookie-Speicher zugreifen oder den Seiteninhalt im Browser überprüfen oder ändern kann.

PKCE-Überlegung

Sie sollten auch PKCE in Betracht ziehen, das hier beschrieben wird: https://www.oauth.com/oauth2-servers/pkce/

Wenn Sie auch den Autorisierungsserver implementieren, gibt https://www.oauth.com/oauth2-servers/oauth-native-apps/checklist-server-support-native-apps/ an , dass Sie dies tun sollten

  • Ermöglichen Sie Clients, benutzerdefinierte URL-Schemata für ihre Umleitungs-URLs zu registrieren.
  • Unterstützt Loopback-IP-Umleitungs-URLs mit beliebigen Portnummern, um Desktop-Apps zu unterstützen.
  • Gehen Sie nicht davon aus, dass native Apps ein Geheimnis für sich behalten können. Fordern Sie alle Apps auf, anzugeben, ob sie öffentlich oder vertraulich sind, und geben Sie nur vertrauliche Apps an Kundengeheimnisse weiter.
  • Unterstützen Sie die PKCE-Erweiterung und fordern Sie, dass öffentliche Clients sie verwenden.
  • Versuchen Sie zu erkennen, wann die Autorisierungsoberfläche in die Webansicht einer nativen App eingebettet ist, anstatt in einem Systembrowser gestartet zu werden, und lehnen Sie diese Anforderungen ab.

Überlegungen zu Webansichten

Es gibt viele Beispiele in freier Wildbahn, die Web Views verwenden, z. B. einen eingebetteten Benutzeragenten. Dieser Ansatz sollte jedoch vermieden werden (insbesondere, wenn die App nicht von Erstanbietern stammt). In einigen Fällen kann die Verwendung einer API als Auszug für Sie verboten werden unten von hier demonstriert

Jeder Versuch, die OAuth 2.0-Authentifizierungsseite einzubetten, führt dazu, dass Ihre Anwendung von der Fitbit-API gesperrt wird.

Aus Sicherheitsgründen muss die OAuth 2.0-Autorisierungsseite in einer dedizierten Browseransicht angezeigt werden. Fitbit-Benutzer können nur bestätigen, dass sie sich bei der echten Fitbit.com-Website authentifizieren, wenn sie über die vom Browser bereitgestellten Tools verfügen, z. B. die URL-Leiste und TLS-Zertifikatinformationen (Transport Layer Security).

Für native Anwendungen bedeutet dies, dass die Autorisierungsseite im Standardbrowser geöffnet werden muss. Native Anwendungen können benutzerdefinierte URL-Schemata als Umleitungs-URIs verwenden, um den Benutzer vom Browser zurück zur Anwendung umzuleiten, die um Erlaubnis bittet.

iOS-Anwendungen verwenden möglicherweise die SFSafariViewController-Klasse, anstatt die App auf Safari umzustellen. Die Verwendung der WKWebView- oder UIWebView-Klasse ist verboten.

Android-Anwendungen verwenden möglicherweise benutzerdefinierte Chrome-Registerkarten, anstatt die App auf den Standardbrowser umzuschalten. Die Verwendung von WebView ist verboten.

Zur weiteren Verdeutlichung finden Sie hier ein Zitat aus diesem Abschnitt eines früheren Entwurfs des oben angegebenen Best-Practice-Links

Eingebettete Benutzeragenten, die üblicherweise mit Webansichten implementiert werden, sind eine alternative Methode zum Autorisieren nativer Apps. Sie sind jedoch per Definition für die Verwendung durch Dritte unsicher. Der Benutzer meldet sich mit seinen vollständigen Anmeldeinformationen an, um sie dann auf weniger leistungsfähige OAuth-Anmeldeinformationen zu reduzieren.

Selbst wenn sie von vertrauenswürdigen Erstanbieter-Apps verwendet werden, verletzen eingebettete Benutzeragenten das Prinzip der geringsten Berechtigungen, indem sie leistungsfähigere Anmeldeinformationen als erforderlich erhalten, wodurch möglicherweise die Angriffsfläche vergrößert wird.

In typischen Webansicht-basierten Implementierungen eingebetteter Benutzeragenten kann die Hostanwendung: jeden im Formular eingegebenen Tastenanschlag protokollieren, um Benutzernamen und Kennwörter zu erfassen; Formulare automatisch einreichen und Einwilligung des Benutzers umgehen; Kopieren Sie Sitzungscookies und verwenden Sie sie, um authentifizierte Aktionen als Benutzer auszuführen.

Wenn Benutzer dazu ermutigt werden, Anmeldeinformationen in einer eingebetteten Webansicht ohne die übliche Adressleiste und andere Identitätsfunktionen einzugeben, über die Browser verfügen, kann der Benutzer nicht erkennen, ob sie sich bei der legitimen Website anmelden, und selbst wenn dies der Fall ist, werden sie geschult dass es in Ordnung ist, Anmeldeinformationen einzugeben, ohne die Site zuerst zu validieren.

Abgesehen von den Sicherheitsbedenken teilen Webansichten den Authentifizierungsstatus nicht mit anderen Apps oder dem Systembrowser, sodass sich der Benutzer für jede Autorisierungsanforderung anmelden muss, was zu einer schlechten Benutzererfahrung führt.

Aus den oben genannten Gründen wird die Verwendung eingebetteter Benutzeragenten NICHT EMPFOHLEN, es sei denn, eine vertrauenswürdige Erstanbieter-App fungiert als externer Benutzeragent für andere Apps oder bietet eine einmalige Anmeldung für mehrere Erstanbieter-Apps.

Autorisierungsserver sollten in Betracht ziehen, Schritte zu unternehmen, um Anmeldungen über eingebettete Benutzeragenten zu erkennen und zu blockieren, die nach Möglichkeit nicht ihre eigenen sind.

Einige interessante Punkte werden auch hier angesprochen: /security/179756/why-are-developers-using-embedded-user-agents-for-3rd-party-auth-what-are-the- ein


3
Google entfernt die Unterstützung für Webviews am 20. April 2017. developer.googleblog.com/2016/08/…
Matt C


Danke @KostiantynSokolinskyi, entsprechend bearbeitet mit Link für RFC, der nicht mehr Entwurf ist
Matt C

@MattC Wie kann die Registrierung eines neuen Benutzers am besten implementiert werden? Sollen wir das in der App oder auf dem IDP machen? Ist es möglich, sich automatisch im Benutzerpostregister anzumelden? stackoverflow.com/questions/60187173/…
Yashvit

Entschuldigung, ich bin verwirrt über einige Details ... Könnten Sie bitte einen Blick darauf werfen? Vielen Dank! link ---> stackoverflow.com/q/61313694/4619958
ch271828n

25

Leider glaube ich nicht, dass es eine klare Antwort auf diese Frage gibt. Hier sind jedoch die Optionen, die ich identifiziert habe:

  • Wenn es in Ordnung ist, den Benutzer nach seinen Anmeldeinformationen zu fragen, verwenden Sie die Anmeldeinformationen für das Kennwort des Ressourcenbesitzers . Dies ist jedoch aus bestimmten Gründen möglicherweise nicht möglich, nämlich

    • Benutzerfreundlichkeits- oder Sicherheitsrichtlinien verbieten das Einfügen des Passworts direkt in die App
    • Der Authentifizierungsprozess wird an einen externen Identitätsanbieter delegiert und muss über einen HTTP-Redirect-basierten Flow (z. B. OpenID, SAMLP oder WS-Federation) ausgeführt werden.
  • Wenn die Verwendung eines browserbasierten Ablaufs erforderlich ist, verwenden Sie den Autorisierungscode- Ablauf . Hier ist die Definition von redirect_urieine große Herausforderung, für die es folgende Optionen gibt:

    • Verwenden Sie die unter https://developers.google.com/accounts/docs/OAuth2InstalledApp beschriebene Technik , bei der eine spezielle redirect_uri(zurn:ietf:wg:oauth:2.0:oob ) dem Autorisierungsendpunkt signalisiert, den Autorisierungscode anzuzeigen, anstatt zur Client-App umzuleiten. Der Benutzer kann diesen Code manuell kopieren oder die App kann versuchen, ihn aus dem HTML-Dokumenttitel abzurufen.
    • Verwenden Sie einen localhostServer am Gerät (die Portverwaltung ist möglicherweise nicht einfach).
    • Verwenden Sie ein benutzerdefiniertes URI-Schema (z. B. myapp://...), das bei einer Dereferenzierung einen registrierten "Handler" auslöst (die Details hängen von der mobilen Plattform ab).
    • Verwenden Sie, falls verfügbar, eine spezielle " Webansicht ", z. B. den WebAuthenticationBroker unter Windows 8, um die HTTP-Umleitungsantworten zu steuern und darauf zuzugreifen.

Hoffe das hilft

Pedro


Danke Pedro für die Eingabe!. Ja, es sieht so aus, als wäre der Autorisierungscode-Fluss mit dem benutzerdefinierten URI-Schema oder die Webansicht hier die beste Option.
Pablo Cibraro

1
Es hängt alles davon ab, ob der Client das Kennwort in eine Webansicht oder in die Client-App eingeben soll. Wenn möglich, würde ich die Client-App bevorzugen - dann tausche das Geheimnis sofort mit einem Zugriffs- / Aktualisierungstoken aus.
Privileg

Danke Dominick!. Mein Kunde verwendet ADFS zur Authentifizierung der Benutzer, daher möchten sie die Anmeldeinformationen auf der Anmeldeseite eingeben. Die
Webansicht

5
Ich bin gespannt, warum Sie den "Authorization Code Flow" empfehlen würden. Würden Sie nicht client_secret und client_id benötigen, um den Code gegen ein access_token auszutauschen? Ich dachte, der "implizite" Fluss wurde für diese Szenarien entwickelt, da keine Geheimnisse im Gerät gespeichert werden müssen.
Eugenio Pace

1
implizit unterstützt keine Aktualisierungstoken OOB. In Pablos Szenario würde ich den RO-Fluss eindeutig empfehlen. Klingt so, als hätte das Unternehmen Apps für dasselbe Unternehmens-Backend bereitgestellt.
wenigsten Privileg

9

TL; DR: Verwenden Sie Authorization Code Grant mit PKCE

1. Impliziter Grant-Typ

Der implizite Grant-Typ ist bei mobilen Apps sehr beliebt. Aber es sollte nicht so verwendet werden. Es gibt Sicherheitsbedenken in Bezug auf die Weiterleitung. Justin Richer sagt :

Das Problem tritt auf, wenn Sie feststellen, dass es im Gegensatz zu einer Remote-Server-URL keine zuverlässige Möglichkeit gibt, sicherzustellen, dass die Bindung zwischen einem bestimmten Umleitungs-URI und einer bestimmten mobilen Anwendung eingehalten wird. Jede App auf dem Gerät kann versuchen, sich selbst in den Umleitungsprozess einzufügen und den Umleitungs-URI zu bedienen. Und raten Sie mal: Wenn Sie den impliziten Ablauf in Ihrer nativen Anwendung verwendet haben, haben Sie dem Angreifer nur Ihr Zugriffstoken übergeben. Ab diesem Zeitpunkt gibt es keine Wiederherstellung mehr - sie haben das Token und können es verwenden.

Und zusammen mit der Tatsache, dass Sie das Zugriffstoken nicht aktualisieren können, sollten Sie es besser vermeiden.

2. Berechtigungscode Grant-Typ

Die Erteilung des Autorisierungscodes erfordert ein Kundengeheimnis. Sie sollten jedoch keine vertraulichen Informationen im Quellcode Ihrer mobilen App speichern. Leute können sie extrahieren. Um das Client-Geheimnis nicht preiszugeben, müssen Sie einen Server als Vermittler betreiben, wie Facebook schreibt :

Wir empfehlen, App Access Tokens nur direkt von den Servern Ihrer App aus zu verwenden, um die beste Sicherheit zu gewährleisten. Für native Apps empfehlen wir, dass die App mit Ihrem eigenen Server kommuniziert und der Server dann die API-Anforderungen mithilfe des App-Zugriffstokens an Facebook sendet.

Keine ideale Lösung, aber es gibt eine neue, bessere Möglichkeit, OAuth auf Mobilgeräten auszuführen: Proof Key für Code Exchange

3. Berechtigungscode-Gewährungstyp mit PKCE (Proof Key for Code Exchange)

Aus den Einschränkungen heraus wurde eine neue Technik erstellt, mit der Sie den Autorisierungscode ohne Client-Geheimnis verwenden können. Sie können den vollständigen RFC 7636 oder diese kurze Einführung lesen .

PKCE (RFC 7636) ist eine Technik zum Sichern öffentlicher Clients, die kein Clientgeheimnis verwenden.

Es wird hauptsächlich von nativen und mobilen Apps verwendet, aber die Technik kann auch auf jeden öffentlichen Client angewendet werden. Es erfordert zusätzliche Unterstützung durch den Autorisierungsserver, sodass es nur von bestimmten Anbietern unterstützt wird.

von https://oauth.net/2/pkce/


-3

Die Verwendung einer Webansicht in Ihrer mobilen Anwendung sollte eine kostengünstige Möglichkeit sein, das OAuth2.0-Protokoll auf einer Android-Plattform zu implementieren.

Das Feld redirect_uri ist meiner Meinung http://localhostnach eine gute Wahl, und Sie müssen keinen HTTP-Server in Ihre Anwendung portieren, da Sie die Implementierung der onPageStartedFunktion in der WebViewClientKlasse überschreiben und das Laden der Webseite beenden können, http://localhostnachdem Sie den urlParameter überprüft haben .

public void onPageStarted(final WebView webView, final String url,
        final Bitmap favicon) {}

3
Best Practices für native Apps mit OAuth2: tools.ietf.org/html/draft-wdenniss-oauth-native-apps
Matt C

1
Wie Matt C oben sagte. Webansichten sind eine schlechte Idee für mobile Apps. Sie sind unsicher, ermöglichen der App den Zugriff auf die Anmeldeinformationen (also nicht sicherer als RO) und ermöglichen Benutzern nicht, Domänen- und TLS-Zertifikate zu überprüfen. Verwenden Sie den Grant-Typ "Auth Code" mit einem benutzerdefinierten URI-Handler und stellen Sie sicher, dass Sie Proof Code for Key Exchange (PKCE) verwenden , um zu verhindern, dass schädliche Apps auf Ihrem Telefon den Auth-Code abfangen und Zugriff auf Ihre API erhalten.
ChrisC

2
Die aktualisierte Version des Entwurfs des Best Practices-Dokuments zu OAuth 2.0 für native Apps finden Sie unter tools.ietf.org/html/draft-ietf-oauth-native-apps
Jeff Olson,

-4

Die reibungsloseste und am einfachsten zu implementierende Benutzererfahrung für die Authentifizierung besteht darin, eine Webansicht in Ihre App einzubetten. Verarbeiten Sie die Antworten, die von der Webansicht vom Authentifizierungspunkt empfangen wurden, und erkennen Sie Fehler (Benutzerabbruch) oder Genehmigung (und extrahieren Sie Token aus URL-Abfrageparametern). Und ich denke, das können Sie tatsächlich auf allen Plattformen. Ich habe diese Arbeit erfolgreich für die folgenden gemacht: iOS, Android, Mac, Windows Store 8.1 Apps, Windows Phone 8.1 App. Ich habe dies für die folgenden Dienste getan: Dropbox, Google Drive, Onedrive, Box, Basecamp. Für die Nicht-Windows-Plattformen habe ich Xamarin verwendet, das angeblich nicht die gesamten plattformspezifischen APIs verfügbar macht, aber genug verfügbar gemacht hat, um dies zu ermöglichen. Es ist also eine ziemlich zugängliche Lösung, selbst aus plattformübergreifender Sicht, und Sie tun es nicht.


Wir bieten eine komfortable Benutzererfahrung und werden feststellen, dass sich die Branche von diesem Ansatz entfernt. Da Webansichten die Möglichkeit eröffnen, Kennwörter zu gefährden, würde ich die App deinstallieren, wenn ich von einem eingebetteten Benutzeragenten nach Anmeldeinformationen gefragt werde. Einige APIs verbieten jetzt sogar solche Integrationen wie diese dev.fitbit.com/docs/oauth2
Matt C

Best Practices für native Apps mit OAuth2: tools.ietf.org/html/draft-wdenniss-oauth-native-apps
Matt C

Ich sehe nicht ein, wie ein oauth-fähiger Dienst diesen Ansatz verbieten könnte. Es ist nicht nachweisbar und sicher ... Einige oauth-fähige Dienste bieten plattformspezifische Clients, um die Authentifizierung zu vereinfachen, und solche Clients tun tatsächlich das, was ich hier beschrieben habe (zeigen Sie eine eingebettete Webansicht an und verfolgen Sie URL-Änderungen). Die von Ihnen verlinkte Best Practice empfiehlt dasselbe: Verwenden Sie einen Systembrowser oder eine eingebettete Webansicht. Welches Argument greifen Sie mit meiner Antwort an? Es ist unklar.
Radu Simionescu

Kein Angriff beabsichtigt, nur das Problem hervorzuheben. Der Link besagt, dass es zwei Ansätze gibt, die Sie erwähnen, aber nur ein externer Benutzeragent kann als sicher angesehen werden. Insbesondere heißt es, dass die Optionen für native Apps "über einen eingebetteten Benutzeragenten oder einen externen Benutzeragenten" erfolgen. In diesem Dokument wird ein externer empfohlen Benutzeragenten wie In-App-Browser-Registerkarten sind die einzige sichere und verwendbare Option für OAuth. "
Matt C

Weiteres Zitat "In typischen Web-View-basierten Implementierungen eingebetteter Benutzeragenten kann die Host-Anwendung: jeden im Formular eingegebenen Tastendruck protokollieren, um Benutzernamen und Kennwörter zu erfassen; Formulare automatisch senden und die Zustimmung des Benutzers umgehen" ....... "Die Verwendung eingebetteter Benutzeragenten wird NICHT EMPFOHLEN, es sei denn, eine vertrauenswürdige Erstanbieter-App fungiert als externer Benutzeragent für andere Apps oder bietet eine einmalige Anmeldung für mehrere Erstanbieter-Apps. Autorisierungsserver sollten Schritte in Betracht ziehen Erkennen und Blockieren von Anmeldungen über eingebettete Benutzeragenten, die nach Möglichkeit keine eigenen sind. "
Matt C
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.