Autentizace uživatelů pomocí OpenID

Martin Pešout   26. 1. 2011


Tento článek by měl v prvé řadě představit službu OpenID, která umožňuje používat jeden uživatelský účet pro více webových služeb. Zejména by měl pomoci všem vývojářům v programovacím jazyce PHP, kteří se rozhodli přihlašování pomocí OpenID integrovat do svých stránek.

Co si lze představit pod pojmem OpenID?

Pokud provozujete webovou službu, je pro Vás rozhodujícím faktorem návštěvnost stránek. Pokud navíc požadujete registraci, je trendem poslední doby celý tento proces maximálně zjednodušovat. Uživatelé nechtějí vyplňovat zbytečné údaje, nechtějí se zdržovat vyplňováním dlouhých formulářů. S postupem času navíc přibývá počet přístupových údajů, které je nutné si pamatovat. Z toho důvodu vznikla služba OpenID, která se snaží všechny tyto zmíněné problémy odstranit a vše maximálně zjednodušit.

Wikipedie tento pojem popisuje následovně:

OpenID je otevřený, decentralizovaný standard pro autentizaci uživatelů. Umožňuje používat jeden uživatelský účet pro více služeb, například pro různé webové služby od různých poskytovatelů. OpenID má tvar unikátního URL, ke kterému je přiřazeno heslo. Webová stránka, která umožňuje autentizaci uživatelů pomocí OpenID při přihlašování uživatele přesměruje požadavek na ověření identity na správce OpenID účtu a ten vrátí informaci o povolení či zamítnutí žádosti o autentizaci. Přenos autentizačních a autorizačních informací zajišťuje protokol SAML.

Co je nutné zjistit na začátku vývoje?

Aby bylo vůbec možné testovat přihlašování přes OpenID, je nutné se nejprve zaregistrovat u některého z poskytovatelů OpenID. Je jich celá řada. Příkladem může být:

Já jsem použil zahraniční MyOpenID. Po zaregistrování doporučuji ještě službu správně nastavit, ať můžete ozkoušet všechny její možnosti. Přidejte novou osobu, která bude vystupovat v rámci Vašeho účtu (v menu Your Account > Registration Personas), jak je možné vidět na přiloženém obrázku. Této osobě nastavte také emailovou adresu. Díky tomu, bude možné na Vašich stránkách po přihlášení zjistit emailovou adresu nového uživatele, kterou můžete v budoucnu využít například k zasílání novinek.

Registrace osoby přes službu MyOpenID

Jak to v praxi funguje?

  1. Vyplním v přihlašovacím formuláři na Vašich stránkách přidělené OpenID.
  2. Systém přesměruje na server poskytovatele služby OpenID
  3. Budete vyzvání k zadání hesla. V budoucnu je možné tento krok přeskočit tím, že potvrdíte, aby všechny požadavky z Vašich stránek a vztahující se k Vašemu OpenID automaticky systém bral za schválené. Ale z hlediska větší bezpečnosti doporučuji pokaždé nechat možnost zadávat heslo.
  4. Pokud bude heslo ověřeno, zobrazí se tlačítko, které Vás přesměruje zpět na Vaše stránky s informací, že všechno proběhlo v pořádku.
  5. Systém tento požadavek zpracuje. Pokud to je Vaše první přihlášení, tak Vás zároveň zaregistruje. Pokud jste se někdy v minulosti tímto způsobem přihlašoval, tak Vás rovnou přihlásí.
  6. Vaše stránky MOHOU získat od poskytovatele OpenID další informace, se kterými lze dále pracovat - e-mail, adresa apod. Toto však není podmínkou. Ne každý má tyto údaje uvedené.

Začínáme implementovat

Pomocné knihovny

Abych nemusel veškerou komunikaci programovat od začátku, použil jsem jednu z dostupných knihoven pro práci s OpenID. V mém případě se jedná o hojně používanou knihovnu SimpleOpenID, která je k dispozici ke stažení třeba na webu www.phpclasses.org. Knihovnu není třeba nijak speciálně nastavovat.

Ukládání do databáze

Pokud máme systém, který umožní zákazníkům registraci, bude pravděpodobně svázán i s databází. Veškeré detaily uživatele budeme evidovat v některé tabulce. Pro jednoduchost uvažujme tabulku users. Pro potřeby práce s OpenID musíme udělat následující rozšíření:

alter table users add column identity char(255) not null;

create table user_openids
(
  id int not null auto_increment,
  identity char(255) not null, # toto je naše klíčová položka
  openid char(255) not null,
  server char(255) not null,  

  primary key (id),
  index openid (openid)
);

Sloupec identity v tabulce users je klíč, který dovoluje uživatelům mít několik několik OpenID ukazujících na jednu společnou identitu.

Tabulka user_openids je důležitá, protože dovoluje následující vztahy:

  • OpenID http://test.myopenid.com/ bude mít přiřazenu identitu test.myopenid.com
  • OpenID test.myopenid.com bude mít přiřazenu identitu test.myopenid.com
  • OpenID http://test.myopenid.com bude mít přiřazenu identitu test.myopenid.com
  • OpenID http://test.com bude mít přiřazenu identitu test.myopenid.com(v určitých případech)
  • apod.

Jak lze tedy vidět OpenID je v podstatě adresa. Je tedy možné různé tvary OpenID adresy reprezentovat jako jednu identitu. Tabulka user_openids nám dovolí uložit tyto různé vztahy. Při příštím přihlášení stejného uživatele nemusíme zjišťovat znovu identitu, ale pouze ji dohledáme v databázi. Samotné zjištění identity (např. identita test.myopenid.com z OpenID http://test.myopenid.com/) se totiž děje pomocí dalšího HTTP požadavku, takže si ušetříme nějakou režii.

Zpracování požadavků

Počáteční požadavek na zpracování OpenID

V prvé řadě je potřeba na stránku umístit přihlašovací formulář, který se postará o odeslání příslušného OpenID identifikátoru. Následuje ukázka kódu na zpracování takto odeslaných dat:

require('class.openid.v2.php');

if ($_POST) {
    $openid = new OpenIDService();
    $openid->SetIdentity($_POST['openid_identifier']); # náš OpenID identifikátor zadaný do formuláře
     $openid->SetTrustRoot('http://' . $_SERVER["HTTP_HOST"]); # URL portálu
     $openid->SetApprovedURL('http://' . $_SERVER["HTTP_HOST"] . '/prihlaseni-openid'); # URL na kterou přesměrujeme zpět po úspěšném ověření OpenID 

    $openid->SetRequiredFields(array('email'));
    $openid->SetOptionalFields(array('fullname'));

    if (list($server, $identity) = get_openid_server($_POST['openid_identifier'])) {
        $openid->SetOpenIDServer($server);
        $openid->SetIdentity($identity);

        // přesměrujeme uživatele na stránky poskytovatele OpenID kvůli autentizaci
        $openid->Redirect();
    }
    else {
        if ($server = $openid->GetOpenIDServer()) {
            // optimalizace celého procesu tím, že do budoucna si zjištěné hodnoty zde uložíme
            $identity = $openid->GetIdentity();
            save_openid_server($_POST['openid_identifier'], $server, $identity);

            // přesměrujeme uživatele na stránky poskytovatele OpenID kvůli autentizaci
            $openid->Redirect();
        } else {
            // DOŠLO K CHYBĚ
        }
    }
}

V uvedeném kódu mám 2 pomocné funkce. První z nich je get_openid_server($openid), která vykoná následující SQL dotaz:

SELECT identity, server 
FROM user_openids
WHERE openid LIKE '$openid'

Druhá funkce save_openid_server($openid, $server, $identity) naopak ukládá dohledané údaje k OpenID:

INSERT INTO user_openids
SET openid = '$openid',
    server = '$server',
    identity = '$identity'

Výše uvedený PHP kód nám zjistí server pro danou službu OpenID a přesměruje na něj kvůli ověření identity.

Zpracování odpovědi od OpenID serveru

Pokud se nám podaří ověřit naší identitu na straně poskytovatele OpenID, budeme vyzváni k přesměrování zpět na naší stránku. Přesměrováni budeme na adresu, která byla zadána při prvním zpracování odeslaných dat, pomocí následujícího příkazu:

$openid->SetApprovedURL('http://' . $_SERVER["HTTP_HOST"] . '/prihlaseni-openid'); # URL na kterou přesměrujeme zpět po úspěšném ověření OpenID 

Teď je jen na řadě rozpoznat odpověď od poskytovatele OpenID a podle toho buď uživatele zaregistrovat, přihlásit nebo celou akci zamítnout. O vše se postará následující kód:

require('class.openid.v2.php');

if ($_GET['openid_mode'] == 'id_res') {

    $openid = new OpenIDService();
    $identity = $_GET['openid_identity'];
    $openid->SetIdentity($identity);
    $openid_validation_result = $openid->ValidateWithServer();

    if ($openid_validation_result) {

        $user = load_user_by_openid($identity);
        if (!$user) {

            $create_res = create_user_by_identity($identity, $_GET[openid_sreg_email]);
            if ($create_res) {
                # VŠE PROBĚHLO OK, UŽIVATEL BYL ÚSPĚŠNĚ ZAREGISTROVÁN. NYNÍ PŘIHLÁSÍME UŽIVATELE (třeba do SESSION)
            }
            else {
                # NEPODAŘILO SE UŽIVATELE ZAREGISTROVAT
            }

        }
        else {
            # VŠE PROBLĚHLO OK, PŘIHLÁSÍME UŽIVATELE
        }

    }
    else if ($openid->IsError() == true) {
         # DOŠLO K CHYBĚ, KTERÁ BUDE POPSANÁ V NÁSLEDUJÍCÍCH PROMĚNNÝCH
         $error = $openid->GetError();
         $error_code =  $error['code'];
         $error_string = $error['description'];
    }
    else {
         # CHYBA, NEOPRÁVNĚNÁ AUTORIZACE
    }
}
elseif ($_GET['openid_mode'] == 'cancel') {
    # CHYBA. POKUD DOSTANEME OD SERVERU TUTO ODPOVĚĎ, TAK UŽIVATEL STORNOVAL PŘIHLAŠOVACÍ PROCES
}

I v tomto kódu mám 2 pomocné funkce. První z nich je load_user_by_openid($identity), která vykoná následující SQL dotaz:

SELECT id
FROM users
WHERE identity LIKE '$openid'

Druhá funkce je create_user_by_openid($identity, $email), která zaregistruje nového uživatele do databáze systému. Provede se tedy následující dotaz:

INSERT INTO users
SET email = '$email',
    identity = '$identity'

Výsledek je tedy následující. Po úspěšném zpracování odpovědi od serveru OpenID dostaneme přihlášeného uživatele do našeho systému. Pokud se jednalo o jeho první pokus o přihlášení, tak se skript zároveň postaral o novou registraci.

Co dodat závěrem?

Služba OpenID není rozhodně jediným možným řešením pro snazší přihlášení uživatelů. Poslední dobou se dosti rozšiřuje autentizace pomocí protokolu OAuth(2). Tento druh přístupu však není předmětem tohoto článku, takže se budu této problematice věnovat v některém z mých budoucích příspěvků.