Ověření unikátnosti uživatele pomocí SMS

Školení, která pořádám

Zajistit unikátnost uživatele jde na webu mnoha různými způsoby:

  1. Nastavení cookie se dá obejít nejjednodušeji, řada uživatelů navíc používá víc prohlížečů.
  2. Kontrola IP adresy je nespolehlivá – jednak může jeden uživatel postupně dostat více adres a jednak může mít stejnou adresu více uživatelů.
  3. Kód CAPTCHA za unikátnost uživatele neručí, ale aspoň zabrání automatizovanému útoku.
  4. E-mailových adres může mít uživatel víc – kromě ručního vytvoření může mít doménový koš nebo použít dočasné adresy.
  5. Na externích službách jako Facebook nebo Twitter může mít účtů víc nebo naopak nemusí mít žádný.
  6. Služba MojeID dokáže totožnost uživatele ověřit, zatím ale není příliš rozšířená.
  7. Kvalifikovaný certifikát také není příliš rozšířený, v prohlížeči ho navíc uživatelé nejspíš nebudou umět použít.
  8. Telefonních čísel může mít uživatel víc, ale jejich hromadné získání je poměrně pracné. Někdo telefon naopak nemá.
  9. Poštovní adresa je spolehlivá při doručení dopisu do vlastních rukou, jinak ne.
  10. Doklad totožnosti nemáme jak ověřit.

Jednotlivé způsoby jsou různě spolehlivé, různě dostupné, různě rychlé a různě drahé. Nejspolehlivější a nejdostupnější je poštovní adresa, je ale nejdražší a nejpomalejší. Slušnou spolehlivost a dostupnost nabízí telefonní číslo, které se dá ověřit okamžitě a celkem levně.

Uložení čísla

Telefonní číslo je citlivý údaj, který potřebujeme jen k tomu, abychom na něj odeslali ověřovací zprávu. Vůči uživatelům by tedy bylo slušnější, když bychom si ho neukládali. Zároveň ale potřebujeme ověřit jeho unikátnost – nabízí se využití hašování. Obyčejný haš je ale kvůli malému rozsahu hodnot prolomitelný do 10 s, potřebujeme tedy použít opakované hašování. Předtím je ale potřeba číslo převést na jednotný formát:

<?php
/** Převedení telefonního čísla na jednotný formát
* @param string číslo ve formátu "+123 456" nebo "123 456" (doplní se prefix +420)
* @return string číslo bez mezer a s národním prefixem
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function unifyPhone($number) {
    return preg_replace('~^(?=[^+])~', '+420', preg_replace('~\s+~', '', $number));
}
?>

O skutečnosti, že si telefonní číslo ani sami nikam neukládáme, můžeme uživatele informovat, takže nám pak číslo svěří s větším klidem.

Odeslání SMS

V roce 2003 jsem SMS odesílal tak, že jsem si koupil GSM modem, sériovým kabelem ho připojil k serveru a komunikoval s ním pomocí AT příkazů. Dnes už je situace naštěstí lepší a existuje několik služeb, které odeslání SMS umožňují online. Jednou z nich je sms.sluzba.cz. Po zřízení účtu a zaplacení kreditu lze SMS odesílat pomocí jednoduchého API.

<?php
/** Odeslání SMS pomocí sms.sluzba.cz
* @param string telefonní číslo ve formátu "+420123456789"
* @param string zpráva bez diakritiky do délky 459 znaků
* @return SimpleXMLElement s elementy <id> a <message> nebo false v případě chyby
* @uses SMS_SLUZBA_LOGIN
* @uses SMS_SLUZBA_PASSWORD
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function sms_sluzba($number, $message) {
    $context = stream_context_create(array('http' => array(
        'method' => 'POST',
        'header' => 'Content-Type: application/x-www-form-urlencoded',
        'content' => http_build_query(array(
            'msg' => $message,
            'msisdn' => $number,
            'act' => 'send',
            'login' => SMS_SLUZBA_LOGIN,
            'auth' => md5(md5(SMS_SLUZBA_PASSWORD) . SMS_SLUZBA_LOGIN . 'send' . substr($message, 0, 31)),
        )),
    )));
    libxml_set_streams_context($context);
    return simplexml_load_file('https://smsgateapi.sluzba.cz/apipost20/sms');
}
?>

Po vytvoření kontextu se zavolá libxml_set_streams_context, která ho nastaví pro další požadavek. To je elegantnější a šetrnější než volání simplexml_load_string(file_get_contents($url, false, $context)).

Text zprávy

Ve zprávě pošleme náhodný kód, který uživatel opíše do formuláře. Kód může být kratičký, protože číslo je unikátní, takže si u něj můžeme evidovat počet pokusů o neúspěšné zadání a při překročení limitu další pokusy odmítnout. Tabulka může vypadat takhle:

idint(11) Auto Increment
phone_md5binary(16)
codedecimal(4,0) unsigned zerofill
attemptstinyint(3) unsigned [0]

Nad sloupcem phone_md5 bude vytvořen unikátní index.

Jakub Vrána, Řešení problému, 8.7.2011, diskuse: 11 (nové: 0)

Diskuse

Kuboslav:

Nebolo by vhodné do toho reguláru vložiť aj kontrolu správnej dĺžky telefónneho čísla?

P.S.: Nemôžem si pomôcť, ale ten komentár nadomnou je dosť okatý pokus o spätný odkaz :-)

Petr Kalivoda:

Jen mu to chudákovi k ničemu nebude - rel="nofollow".

ikona Jakub Vrána OpenID:

Kontrolu čísla provádí přímo sms.sluzba.cz a v případě neplatnosti vrátí chybu. Je zbytečné kontrolu duplikovat ve vlastním kódu.

Spam jsem smazal.

Martin Kopta:

Myslel jsem, že ten regex s číslem je jen ukázkový, a že si ho má každý dopsat, jak potřebuje. Já třeba svoje číslo píšu jako +420.xxx.yyy.zzz, takže v něm \s+ není ani jednou. Spíš bych teda mezinárodní prefix vyřešil nejdřív, a pak využil něco jako \D+.

Délku čísla nemá smysl ověřovat, protože ne ve všech zemích je délka čísel jednotná. Což mě vede k tomu, že u API pro zasílání SMS je vhodné ověřit, jak si poradí s uživatelem s nějakým exotičtějším číslem, pokud se nezaměříte jen na uživatele se zdejšími operátory.

Michal Kutil:

Děkuji za pěkný příspěvek, to horní shrnutí je fajn.

Měl bych námět na pokračování: Jak uživatele jednoznačně identifikovat a ověřovat opakovaně. Tedy jak bránit tomu, aby při opakovaném přístupu ke službě nemohl své ověření delegovat třetí osobě (předpokládám, že uživatel to chce, my naopak ne). Tedy například heslo si uživatelé rádi řeknou. Hardwarový klíč, certifikáty, nebo jednorázová hesla to všechno je přenositelné. Poštovní adresa je zase na pravidelné ověřování pomalá a sms může být v cizině nespolehlivá, néli nedostupná. Vazba na IP adresu a Cookies je spíše vazba na PC než na uživatele. Máte někdo nějaký nápad? Děkuji.

Tom:

Já si myslím že není vůbec špatné podporovat Moje ID.

1. fáze je mail
2. fáze je sms (pokud si to uživatel přeje)
3. fáze je pošta

Čim víc služeb to bude vyžadovat, tim rychleji se to dostane do podvědomí ;)

David Grudl:

Ještě je třeba unikátnost vyžadovat jen v opodstatněných případech. Docela ni vadí, když třeba ke každému účtu ma Twitteru musím mít extra email.

starenka:

Asi to vis, ale pokud mas gmail staci pouzit tecky nebo +:
d.a.v.i.dgrudl@gmail.com
dav.i.dgr.udl@gmail.com
davidgrudl+nettecz@gmail.com
davidgrudl+zeremaledeti@gmail.com

to vsechno smeruje na jednu adresu davidgrudl@gmail.com

Vetsina sluzeb (zamerne?) tohle nepodchycuje a jsou to pro ni unikatni adresy

Lamicz:

"O skutečnosti, že si telefonní číslo ani sami nikam neukládáme, můžeme uživatele informovat, takže nám pak číslo svěří s větším klidem"
Bohužel toto se dnes lidi dočtou téměř všude a pak zjistí, že nějaký hacker se do té databáze dostal a ví o nich vše včetně velikosti trenek... Této hlášce už dnes mnoho lidí nevěří - osobní údaje byly, jsou a budou největším obchodním artiklem :(

ikona Jakub Vrána OpenID:

Podle mě se to neděje. Osobní údaje sbírá samozřejmě kde kdo, ale málokdo se neoprávněně chlubí tím, že by je neukládal. Nebo máš nějaké ukázky webů, které tvrdí, že si data neukládají (a přitom je podezříváš z opaku)?

Sheldon:

Na skrytí čísla nelze použít mcrypt a vytvořit si fc bin2hex. A pak zpět s heslem který si určí uživatel sám? Pak při odcizení databáze s čísly bude dost těžké zjistit co v tabulkách je.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.