Back-End

28 mai, 2014

Criando um sistema de login sem senha

Publicidade

Ano passado meu amigo Dênis Costa me enviou um artigo fantástico sobre um sistema de login sem senha. A ideia principal é: para que pedir uma senha ao usuário se ele vai esquecê-la?. Ou seja, se o “esqueci minha senha” vai ser utilizado com frequência, porque não simplesmente enviar um link de login por e-mail toda vez que usuário quiser acessar o sistema? Achei a ideia genial.

Procurei por alguém que oferecesse o login sem senha como serviço, aos moldes de um “login com Facebook”. Não encontrei. Decidi então desenvolver o Passwordless. Além de se poder utilizar como serviço, o projeto é livre: https://github.com/renzon/pswdless

No processo de desenvolvimento mapiei os status para logar o usuário:

  1. Usuário pede para fazer login em um site;
  2. Site envia chamada para Passwordless;
  3. Passworldless envia e-mail em nome do site;
  4. Usuário acessa e-mail e clica no link;
  5. Passworldless envia noticação ao site;
  6. Site efetua o login.

Desenhando o diagrama de sequência:

auth_seq

Além desse fluxo, também são importantíssimos os requisitos de segurança.

O primeiro deles é evitar o envio de spam. Isso foi mitigado criando um intervalo de tempo mínimo, atualmente 30 minutos, para que se possa enviar um e-mail de login para o mesmo usuário. Segue o teste automático:

def test_spam(self):
        site = mommy.make_one(Site, domain='www.pswd.com')
        user = mommy.make_one(PswdUser)
        ndb.put_multi([site, user])
        create_login = CreateLogin(user, site, 'hook')
        create_login.execute()
        # time.sleep(3)  # giving time because eventual consistency
        validate_cmd = ValidateLoginCall(site.key.id(), site.token, 'http://www.pswd.com/pswdless',
                                         user.key.id())

O segundo requisito seria proteger o link de login. Para isso foram implementadas as seguintes medidas:

  1. Todo link é único, contendo um token aleatório e assinado via hash;
  2. Todo link tem validade. Se em 10 minutos o usuário não clicar nele, ele fica inválido;
  3. Todo link é descartável. Depois de utilizado, não mais é possível fazer login com ele.

Para todas essas medidas foram construídos testes automáticos:

class ValidateLoginLinkTests(GAETestCase):
    def _assert_error(self, token):
        validate_cmd = ValidateLoginLink(token, None)
        validate_cmd.execute()
        self.assertDictEqual({'ticket': 'Invalid Call'}, validate_cmd.errors)
 
    def _assert_wrong_status(self, status):
        login = Login(status=status, hook='https://pswdless.appspot.com/foo')
        login.put()
        cmd = client_facade.sign_dct('ticket', login.key.id())
        cmd.execute()
        self._assert_error(cmd.result)
 
    def test_invalid_token(self):
        self._assert_error('invalid token')
 
    def test_already_clicked(self):
        self._assert_wrong_status(LOGIN_CLICK)
 
    def test_already_got_detail(self):
        self._assert_wrong_status(LOGIN_DETAIL)
 
    def test_not_existing_login(self):
        cmd = client_facade.sign_dct('ticket', 2)
        cmd.execute()
        self._assert_error(cmd.result)

Depois de um certo tempo, outro amigo, o Giovane Liberato, acabou me enviando alguns links sobre o assunto:

  1. https://medium.com/cyber-security/9ed56d483eb
  2. http://lab.vtex.com/blog/como-matamos-a-senha/

Interessante notar a análise no segundo, onde colocaram dados sobre a aceitação dos usuários a um sistema de login sem senha de um comércio eletrônico.

Mas e você, qual sua opinião sobre um sistema de login sem senha?

Até o próximo artigo!