OpenID

Jan Holan       13. 6. 2012       ASP.NET/IIS, Bezpečnost       7037 zobrazení

Mezi výčtem webových technologii pro autentizaci nám nesmí chybět technologie OpenID. Koukneme se jak se tato technologie používá, a představíme si .NET knihovnu DotNetOpenAuth, pomoci které budeme OpenID volat v příkladové ASP.NET aplikaci.

OpenID je v podstatě jednou z prvních technologií, která přišla s myšlenkou centralizace přihlašování na nejrůznějších webech do jednoho místa (Federated Login), uživatel si tak bude pamatovat pouze jediné přihlašovací údaje.

Přihlášení pomoci OpenID

Přihlášení na webech podporující OpenID (OpenID enabled website) pak realizujeme pomoci IDčka ve formě URL, které může obsahovat část jedinečnou pro daného uživatele, například: http://holajan.myopenid.com.  Služba, na kterou je url směrované (v tomto případě myopenid.com) je tzv. OpenID provider (OP). Tento provider provádí ověření uživatele (authentication), k tomu si drží informace pro přihlášení jako je login nebo email a heslo. Někteří provideři navíc umožňují udržovat i informace do Vašeho profilu tj. další doplňující údaje jako je Plné jméno, Alias, Jazyk, Stát apod. Tyto údaje lze při přihlášení také načíst.

Pozn.: Web, který provádí OpenID request na providera pro ověření uživatele se nazývá Relying Party (RP).

Celý proces přihlášení ve zkratce probíhá takto:

  1. Uživatel zadá své OpenID na přihlašovací stránce webu (Relying Party)
  2. Dojde k přesměrování browseru na příslušného OpenID providera
  3. Uživatel se na stránkách daného providera přihlásí (nejčastěji pomoci username a hesla OpenID účtu). Pokud má uživatel přihlášení zapamatované např. z minula, je tento krok proveden automaticky.
  4. Povolíme, že naše identita daného providera, může být použita pro web, na který se přihlašujeme.
  5. Dojde k přesměrování browseru zpět na původní web a předání identity.

OpenID providers

OpenID providerů v dnešní době existuje opravdu hodně, u některých dokonce (i když ho používáte) ani nemusíte vědět, že se jedná zároveň i o OpenID providera (například Google Account Federated Login). Zde namátkou uvedu některé z nich a jejich přihlašovací ID.

myopenid myOpenID http://{username}.myopenid.com
http://www.myopenid.com
claimid.ico claimID http://claimid.com/{username}
verisign.ico VeriSign http://{username}.pip.verisignlabs.com
mojeID-ikona-16x16 mojeID http://{username}.mojeid.cz
aol.ico Aol. http://openid.aol.com/{username}
google.ico Google https://www.google.com/accounts/o8/id
googleplus.ico Google+ Profile http://www.google.com/profiles/{userid}
http://profiles.google.com/{userid}
http://plus.google.com/profiles/{userid}
yahoo.ico Yahoo! http://me.yahoo.com
wordpress.ico WordPress http://{username}.wordpress.com
 myspace http://www.myspace.com/{username}

Pozn.: Všimněte si, že u některých ID providerů není v URL žádná identifikace uživatele, pak se při přihlašování na stránkách providera musí zadat i uživatelské jméno nebo email. Takto se ale přihlásíme jednou poprvé, a naše přihlášení se zapamatuje a je využíváno i na přihlašování na jiné weby (Single Sign On). Toho, že je ID neměnné, se také někdy využívá v různých přihlašovacích formulářů a OpenID selectorech, kde se pak OpenID nezadává, ale je tam přímo na tlačítko zabudované volání daného providera.

Pozn. 2: Služba mojeID od českého sdružení CZ.NIC je kompatibilní s OpenID tj. lze se přihlásit na stránkách, které podporují OpenID pomoci mojeID (opačně ale ne).

Pozn. 3: Facebook, Twitter nebo Windows Live ID nejsou OpenID provideři, ty pracují na jiném protokolu OAuth 2.0. Takové přihlášení jsme si uvedli zde a zde.

Dále je v OpenID možnost mít jako hodnotu vašeho ID přímo URL vaší domény (blog, stránky vaší společnosti atd.), to lze zajistit pomoci OpenID delegation, kterou někteří provideři umožňují nastavit. Poslední možností je také vytvoření providera vlastního.

Knihovna DotNetOpenAuth

Pro volání OpenID z .NETu existuje (v podstatě jediná) knihovna s názvem DotNetOpenAuth (dříve se jmenovala DotNetOpenId). Jedná se o open source knihovnu podporující OpenID, OAuth a ICard. Verze knihovny v době psaní tohoto článku je v4.0.1, knihovna je dostupná i jako NuGet balíček (PM> Install-Package DotNetOpenAuth).

Volání OpenID v ASP.NET pomoci této knihovny lze buď přímo z kódu pomoci třídy OpenIdRelyingParty, nebo také pomoci ASP.NET kontrolů OpenIdTextBox a OpenIdLogin (případně OpenIdAjaxTextBox), které knihovna obsahuje. Živé demo je dostupné zde.

Příklad přihlášení pomoci OpenID

OpenIDSample

Zde je k dispozici můj přiklad na přihlášení pomoci OpenID využívající právě knihovnu DotNetOpenAuth. Ukážeme si některé části kódu.

Stránka Login.aspx obsahuje tyto kontroly umožňující zadat OpenID pro přihlášení:

<rp:OpenIdTextBox runat="server" ID="OpenIdTextBox" LogOnMode="None" CssClass="openIDBox" />
<asp:Button ID="LoginButton" runat="server" Text="Login" OnClick="LoginButton_Click" /><br />
<asp:CustomValidator ID="openidValidator" runat="server" ControlToValidate="OpenIdTextBox" Display="Dynamic" ErrorMessage="Invalid OpenID Identifier"
    EnableViewState="false" OnServerValidate="openidValidator_ServerValidate" />
<asp:RequiredFieldValidator ID="openidRequiredValidator" runat="server" ControlToValidate="OpenIdTextBox" Display="Dynamic" ErrorMessage="OpenID is empty" />

A kód události click tlačítka LoginButton je následující:

protected void LoginButton_Click(object sender, EventArgs e)
{
    if (!this.Page.IsValid)
    {
        return;
    }

    try
    {
        OpenIdLogOn(OpenIdTextBox.Identifier);
    }
    catch (ProtocolException ex)
    {
        //The user probably entered an Identifier that was not a valid OpenID endpoint.
        this.openidValidator.Text = ex.Message;
        this.openidValidator.IsValid = false;
    }
}

private void OpenIdLogOn(Identifier identifier)
{
    using (var openid = new OpenIdRelyingParty())
    {
        var request = openid.CreateRequest(identifier);
        //Add OpenID extensions you wanted to include in the authentication request.
        request.AddExtension(new ClaimsRequest
        {
            Email = DemandLevel.Require,
            FullName = DemandLevel.Require,
            Nickname = DemandLevel.Request,
            Language = DemandLevel.Request,
            Country = DemandLevel.Request,
            Gender = DemandLevel.Request
        });

        //We are making use of OpenID Attribute Exchange 1.0 to fetch additional data fields from the OpenID Provider 
        var fetchRequest = new FetchRequest();
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Email);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.FullName);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.First);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.Last);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Name.Alias);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Preferences.Language);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.HomeAddress.Country);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Person.Gender);
        fetchRequest.Attributes.AddRequired(WellKnownAttributes.Contact.Web.Homepage);

        request.AddExtension(fetchRequest);

        //Send your visitor to their Provider for authentication. 
        request.RedirectToProvider();
    }
}

V metodě OpenIdLogOn, která se stará o vytvoření OpenID requestu (AuthenticationRequest) a jeho vyvolání, se definují pomoci tzv. OpenID extensions, které údaje vyžadujeme nebo chceme od OpenID providera vrátit. Zde máme dvě možnosti: OpenID Simple Attribute Registration (sreg) - ClaimsRequest a OpenID Attribute Exchange 1.0 (Ax) - FetchRequest. Ne každý provider podporuje obě možnosti, proto v kódu používám obě, např. pro získání údajů Google profilu je potřeba použít Attribute Exchange protocol (viz. tento odkaz).

Obecně ale není garantováno, že se vůbec nějaké doplňující údaje vrátí a tak náš web musí být vždy připraven i na to, že žádná data nedostane. Kód zpracování odpovědi např. v Page_Load může vypadat nějak takto:

protected void Page_Load(object sender, EventArgs e)
{
    OpenIdRelyingParty openid = new OpenIdRelyingParty();
    var response = openid.GetResponse();
    if (response != null)   // OpenId redirection callback
    {
        switch (response.Status)
        {
            case AuthenticationStatus.Authenticated:
                var userInfo = new OpenIDUserInfo(response);
                Session["OpenIDUserInfo"] = userInfo;

                //Use FormsAuthentication to tell ASP.NET that the user is now logged in, with the OpenID Claimed Identifier as their username.
                FormsAuthentication.RedirectFromLoginPage(response.ClaimedIdentifier, false);
                break;
            case AuthenticationStatus.Canceled:
                this.loginCanceledLabel.Visible = true;
                break;
            case AuthenticationStatus.Failed:
                this.loginFailedLabel.Text = response.Exception.Message;
                this.loginFailedLabel.Visible = true;
                break;
        }
    }
}

a konstruktor pomocného objektu OpenIDUserInfo vypadá takto:

internal OpenIDUserInfo(IAuthenticationResponse response)
{
    this.ClaimedIdentifier = response.ClaimedIdentifier;
    this.OpenID = new Uri(response.ClaimedIdentifier.ToString()).GetLeftPart(UriPartial.Path);
    if (this.OpenID.EndsWith("/", StringComparison.Ordinal))
    {
        this.OpenID = this.OpenID.Substring(0, this.OpenID.Length - 1);
    }
    this.FriendlyIdentifier = response.FriendlyIdentifierForDisplay;
    this.Provider = response.Provider.Uri.AbsoluteUri;

    //Fill from OpenID extension responses included in the authentication assertion.
    var sreg = response.GetExtension<ClaimsResponse>();
    if (sreg != null)
    {
        this.Email = sreg.Email;
        this.FullName = sreg.FullName;
        this.Nick = sreg.Nickname;
        this.Language = sreg.Language;
        this.Country = sreg.Country;
        if (sreg.Gender != null)
        {
            this.Gender = sreg.Gender.Value.ToString();
        }
    }

    var fetch = response.GetExtension<FetchResponse>();
    if (fetch != null)
    {
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Contact.Email)))
        {
            this.Email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Name.FullName)))
        {
            this.FullName = fetch.GetAttributeValue(WellKnownAttributes.Name.FullName);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Name.First)))
        {
            this.FirstName = fetch.GetAttributeValue(WellKnownAttributes.Name.First);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Name.Last)))
        {
            this.LastName = fetch.GetAttributeValue(WellKnownAttributes.Name.Last);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Name.Alias)))
        {
            this.Nick = fetch.GetAttributeValue(WellKnownAttributes.Name.Alias);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Preferences.Language)))
        {
            this.Language = fetch.GetAttributeValue(WellKnownAttributes.Preferences.Language);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country)))
        {
            this.Country = fetch.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Person.Gender)))
        {
            this.Gender = fetch.GetAttributeValue(WellKnownAttributes.Person.Gender);
        }
        if (!string.IsNullOrEmpty(fetch.GetAttributeValue(WellKnownAttributes.Contact.Web.Homepage)))
        {
            this.Web = fetch.GetAttributeValue(WellKnownAttributes.Contact.Web.Homepage);
        }
    }

    if (string.IsNullOrEmpty(this.FullName) && !string.IsNullOrEmpty(this.FirstName) && !string.IsNullOrEmpty(this.LastName))
    {
        this.FullName = this.FirstName + " " + this.LastName;
    }
}

S OpenID dále souvisí:

OpenID Selector

Také můžete najít OpenID Selector. Jedná se o webové UI s grafickými ikonami jednotlivých OpenID providerů, které umožňuje uživateli velice jednoduše zvolit způsob přihlášení, uživatel ani nemusí nic vědět o OpenID jako takovém. Pokud vám nevadí Javascript, určitě se na toto podívejte. Demo je k dispozici zde.

RpxNow

Od firmy Janrain, Inc (myOpenID) existuje služba RpxNow. Podobně jako OpenID selector dává uživateli na výběr z nejrůznějších možností přihlášení, navíc ale kromě OpenID podporuje i jiné mechanismy, zahrnuje tak např. i Facebook, Twitter, Window Live ID, LinkedIn. Příklad implementace je zde. Je zde ale jeden problém při použití, uživatelé se vlastně ale nepřihlašují k vám na stránky, ale na stránky RpxNow, to může být někdy nežádoucí, více zde.

OpenID Connect 1.0

Na stránkách http://openid.net/connect jsou informace o připravovaném OpenID Connect. Jedná se podobný protokol jako OpenID 2.0, ale využívá API-friendly RESTful přístup. A dále obsahuje obdobné pokročilejší možnosti jako OAuth 2.0 protokol (OpenID 2.0 lze integrovat s již starým OAuth 1.0, nazývalo se to OpenID/OAuth hybrid).


Zde jsou dále odkazy na další příklady:
http://www.west-wind.com/weblog/posts/2009/Sep/17/Integrating-OpenID-in-an-ASPNET-MVC-Application-using-DotNetOpenAuth http://blog.tchami.com/?tag=/DotNetOpenAuth
http://bhaidar.net/post/2011/04/04/OpenID-Single-Sign-On-ASPNET-Web-Forms.aspx
http://www.code-magazine.com/Article.aspx?quickid=0907061
http://www.punjabisonline.net/openid-oauth-login-buttons

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback