Ano passado meu amigo Dênis Costa me
enviou um post 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
https://github.com/renzon/pswdless
No processo de desenvolvimento mapiei
os status para logar o usuário:
- Usuário pede para fazer login em um site;
- Site envia chamada para Passwordless;
- Passworldless envia e-mail em nome do site;
- Usuário acessa e-mail e clica no link;
- Passworldless envia noticação ao site.
- Site efetua o login
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:
- Todo link é único, contendo um token aleatório e assinado via hash;
- Todo link tem validade. Se em 10 minutos o usuário não clicar nele, ele fica inválido;
- 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:
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 post.