Olá a todos,
Continuando nossa série de posts sobre autenticação com Node.JS, vamos expandir um pouco nossas possibilidades de autenticação adicionando uma autenticação externa. Isso é bom porque adiciona uma camada a mais de segurança ao sistema, uma vez que a responsabilidade de autenticação não está mais com você, facilita para o usuário, já que ele só precisa logar uma vez no sistema (A não ser que ele saia do sistema externo), além de ser aumentar a confiança do sistema, já que o usuário vai saber que suas credenciais não vão estar armazenadas no sistema local.
Lembrando que o projeto completo está disponível aqui, e este tutorial foi baseado neste excelente link.
Algo em comum a este tipo de autenticação externa é o protocolo, chamado OAuth. Ele permite que uma aplicação se autentique "em nome do usuário", mesmo sem conhecer as credenciais. Isto é possível porque existem credenciais que registram a "permissão" do usuário para que as informações sejam trocadas. E esta "permissão" independe da senha ou outros tipos de credenciais.
Sendo assim, precisamos criar, na aplicação externa, as nossas credenciais, que vão nos permitir acessar a API da aplicação externa e a mágica acontecer. Primeiro, vamos na página de apps de desenvolvimento do Twitter e após entrarmos, teremos esta página:
Após clicarmos em Create New App, preenchemos as informações necessárias do nosso app. No meu caso, as partes mais importantes ficaram preenchidas assim:
- Nome: node-auth-example
- Callback URL: http://127.0.0.1:3000/login/twitter/callback
Depois, quando clicarmos no botão de confirmar, a app é criada. Na aba Settings, marque a opção "Allow this application to be used to Sign in with Twitter" e e salve as mudanças. Na aba "Keys and Access Tokens", copie o Consumer Key e o Consumer Secret para o arquivo config/config.js no seguinte formato:
secret:{
cookie:'lquercoisaksadnckjadscdscdscndc',
twitter:{
consumer_key:'',
consumer_secret:'',
},
},
Depois, vamos atualizar o nosso modelo de Usuário para armazenar as informações do twitter:
auth:{
local:{
email:String,
password:String,
},
twitter:{
id:String,
token:String,
displayName:String,
username:String,
},
},
Em seguida, as rotas. Vamos precisar de 2 rotas:
- /twitter/connect: Acessar o twitter, obter as informações do usuário e fazer o login,
- /twitter/callback: URL que o Twitter vai usar para voltar à nossa aplicação.
No arquivo config/routes.js, temos:
'/twitter/connect':{
controller:'LoginController',
action:'twitterConnect',
},
'/twitter/callback':{
controller:'LoginController',
action:'twitterCallback',
},
Perceba que a URL de callback deve bater com a URL de callback que você cadastrou no Twitter app.
Depois, vamos instalar o módulo do passport de conexão com o Twitter...
npm install --save passport-twitter
... e atualizar o arquivo passport.js:
passport.use(new TwitterStrategy({
consumerKey: config.secret.twitter.consumer_key,
consumerSecret: config.secret.twitter.consumer_secret,
callbackURL: config.secret.twitter.callback_url,
}, function(token, tokenSecret, profile, done){
process.nextTick(function(){
console.log(profile);
User.findOne({'auth.twitter.id':profile.id}, function(err, user){
if(err) return done(err);
if(user){
return done(null, user);
}else{
var newUser=new User();
newUser.auth.twitter.id=profile.id;
newUser.auth.twitter.token=token;
newUser.auth.twitter.username=profile.username;
newUser.auth.twitter.displayName=profile.displayName;
newUser.save(function(err){
if(err) throw err;
return done(null, newUser)
});
}
});
});
}));
Perceba que o código é muito parecido com a estratégia de signup local.
Depois, atualizaremos o controller de login:
twitterConnect: passport.authenticate('twitter'),
twitterCallback: passport.authenticate('twitter', {
successRedirect: '/dashboard',
failureRedirect: '/',
}),
Para finalizar, vamos atualizar as views para que a autenticação fique completa:
No arquivo views/index.hbs, logo abaixo do link de signup, adicione a seguinte linha:
<a href="/twitter/connect" class="btn btn-info"><span class="fa fa-twitter"></span> Twitter</a>
No arquivo views\dashboard.hbs, adicione o seguinte bloco, após a div das informações locais:
<!--Twitter Information-->
<div class="col-sm-6">
<div class="well">
<h3 class="text-info"><span class="fa fa-twitter"></span> Twitter</h3>
<p>
<strong>id</strong>: {{user.auth.twitter.id}}<br/>
<strong>token</strong>: {{user.auth.twitter.token}}<br/>
<strong>username</strong>: {{user.auth.twitter.username}}<br/>
<strong>displayName</strong>: {{user.auth.twitter.displayName}}<br/>
</p>
</div>
</div>
Agora, basta executar a aplicação. Porém, o código não vai funcionar se digitarmos localhost:3000. Isso porque o Twitter não reconhece a palavra localhost. Então daqui por diante, vamos usar a url por ip para os nossos testes (http://127.0.0.1:3000)
Após clicar no botão do twitter, você será redirecionado para a página de autorização do aplicativo. Após confirmar, você será redirecionado para o dashboard, que vai ficar assim:
Perceba que a área local está com o email e senha em branco. Isso porque a conta do twitter e a conta local estão separadas (o ID local na verdade é o ID do MongoDB do registro dos dados do Twitter). Deste modo, no banco, temos armazenados 2 usuários:
- Usuário A, que é o usuário cadastrado pela autenticação local;
- Usuário B, que é o usuário cadastrado pela autenticação via Twitter.
De fato, nós nunca fizemos nenhum mecanismo de associação de contas. Assim, precisamos criar um mecanismo para associar todas as contas em uma só.
Primeiro, vamos preparar o terreno para a autenticação local (Depois expandimos para as próximas) criando 2 novas rotas:
- /local/link (get e post)
- /local/unlink
No arquivo config/routes.js, adicionamos:
'/local/link':{
controller:'LoginController',
action:'localLink',
policy:'isAuthenticated',
},
'POST /local/link':{
controller:'LoginController',
action:'linkAccount',
policy:'isAuthenticated',
},
'/local/unlink':{
controller:'LoginController',
action:'unlinkAccount',
policy:'isAuthenticated',
},
Em seguida, modificamos o arquivo lib/passport.js, para atualizar a nossa estratégia e contemplar as ações de link e unlink. No final, todas as estratégias vão ter a mesma alteração, que é verificar se o usuário está logado. Caso esteja, basta atualizar o registro com os dados novos de autenticação. Em caso contrário, segue o que estava antes. Aqui será mostrado apenas o código final da estratégia local do signup, que é a única das 2 estratégias locais em que esta alteração será necessária.
passport.use('local-signup', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, function(req,email,password,done){
// é recomendável que todo o procedimento seja feito de forma assíncrona
process.nextTick(function(){
/*
* Verificando se o usuário já está cadastrado
*/
User.findOne({'auth.local.email':email}, function(err,user){
if(err) done(err);
if(!req.user){
// se o usuário existir, exibe uma mensagem de erro.
if(user){
return done(null, false, req.flash('signupMessage', 'Usuário já cadastrado.'));
}else{
// caso contrário, crie o novo usuário.
var newUser=new User();
newUser.auth.local.email=email;
newUser.auth.local.password=newUser.generateHash(password); // a senha deve ser encriptada antes da gravação no banco.
newUser.save(function(err){
if(err) throw err;
return done(null, newUser, req.flash('signupMessage', 'Usuário cadastrado com sucesso.'));
});
}
}else{
//atualizar o usuário com os dados novos.
var _user=req.user;
_user.auth.local.email=email;
_user.auth.local.password=_user.generateHash(password);
_user.save(function(err){
if(err) throw err;
return done(null, _user, req.flash('signupMessage', 'Usuário atualizado.'));
});
}
});
});
})); // fim da estratégia para o signup.
Agora, atualizamos o arquivo controllers/LoginController para adicionar nossas novas actions (link e unlink):
localLink:function(req,res){
res.render('addAccount');
},
linkAccount:passport.authenticate('local-signup',{
successRedirect:'/dashboard',
failureRedirect:'/local/link',
}),
unlinkAccount:function(req,res){
var _user=req.user;
_user.auth.local.email=undefined;
_user.save(function(err){
if(err) throw err;
res.redirect('/dashboard');
});
},
Como vocês puderam perceber, a view renderizada para a action localLink tem o nome 'addAccount'. Portanto, vamos criar esta view, que é praticamente uma cópia da view de signup:
<div class="col-sm-6 col-sm-offset-3">
<h1><span class="fa fa-sign-in"></span> Add Account</h1>
<form action="/local/link" method="post">
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" name="email">
</div>
<div class="form-group">
<label>Senha</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-warning btn-lg">Cadastrar</button>
</form>
</div>
Para finalizar, alteramos a view de dashboard para refletir as nossas mudanças.
<div class="container">
<div class="page-header text-center">
<h1><span class="fa fa-anchor"></span> Dashboard</h1>
<a href="/signout" class="btn btn-default btn-sm">Logout</a>
<div class="row">
<!--Local information-->
<div class="col-sm-6">
<div class="well">
<h3><span class="fa fa-user"></span> Local</h3>
{{#if user.auth.local.email}}
<p>
<strong>id</strong>: {{user._id}}<br/>
<strong>email</strong>: {{user.auth.local.email}}<br/>
<strong>password</strong>: {{user.auth.local.password}}<br/>
</p>
<a href="/local/unlink" class="btn btn-default">Desassociar conta</a>
{{else}}
<a href="/local/link" class="btn btn-default">Cadastrar conta local</a>
{{/if}}
</div>
</div>
<!--Twitter Information-->
<div class="col-sm-6">
<div class="well">
<h3 class="text-info"><span class="fa fa-twitter"></span> Twitter</h3>
<p>
<strong>id</strong>: {{user.auth.twitter.id}}<br/>
<strong>token</strong>: {{user.auth.twitter.token}}<br/>
<strong>username</strong>: {{user.auth.twitter.username}}<br/>
<strong>displayName</strong>: {{user.auth.twitter.displayName}}<br/>
</p>
</div>
</div>
</div>
</div>
</div>
Agora, quando executarmos nossa aplicação e nos logarmos pelo Usuário B (via Twitter), a tela ficará parecida com essa:
Ou seja, a view verifica se o usuário possui autenticação local. Caso não tenha, o botão de cadastrar autenticação local aparece. Após apertar no botão e cadastrar o email e senha (cadastre um e-mail diferente, para que não ocorram erros), a tela ficará assim:
Se nós clicarmos em Desassociar Conta, tanto o e-mail quanto a senha serão apagados, e a aplicação volta para a tela anterior.
Para associarmos com a conta do Twitter, o processo é semelhante. Primeiramente as rotas:
'/twitter/link':{
controller:'LoginController',
action:'linkTwitter',
policy:'isAuthenticated',
},
'/twitter/link/callback':{
controller:'LoginController',
action:'linkTwitterCallback',
policy:'isAuthenticated',
},
'/twitter/unlink':{
controller:'LoginController',
action:'unlinkTwitter',
policy:'isAuthenticated',
},
Depois, a atualização do arquivo lib/passport.js
passport.use(new TwitterStrategy({
consumerKey: config.secret.twitter.consumer_key,
consumerSecret: config.secret.twitter.consumer_secret,
callbackURL: config.secret.twitter.callback_url,
}, function(token, tokenSecret, profile, done){
process.nextTick(function(){
console.log(profile);
User.findOne({'auth.twitter.id':profile.id}, function(err, user){
if(err) return done(err);
if(!req.user){
if(user){
return done(null, user);
}else{
var newUser=new User();
newUser.auth.twitter.id=profile.id;
newUser.auth.twitter.token=token;
newUser.auth.twitter.username=profile.username;
newUser.auth.twitter.displayName=profile.displayName;
newUser.save(function(err){
if(err) throw err;
return done(null, newUser)
});
}
}else{
var _user=req.user;
_user.auth.twitter.id=profile.id;
_user.auth.twitter.token=token;
_user.auth.twitter.username=profile.username;
_user.auth.twitter.displayName=profile.displayName;
_user.save(function(err){
if(err) throw err;
return done(null, _user);
});
}
});
});
}));
Agora a principal diferença: No controller, utilizaremos a função
authorize, que passa o usuário logado para a estratégia. No final, o código ficará assim: linkTwitter:passport.authorize('twitter', {scope: 'email'}),
linkTwitterCallback:passport.authorize('twitter', {
successRedirect:'/dashboard',
failureRedirect:'/',
}),
Agora, só falta alterar o dashboard:
<!--Twitter Information-->
<div class="col-sm-6">
<div class="well">
<h3 class="text-info"><span class="fa fa-twitter"></span> Twitter</h3>
{{#if user.auth.twitter.id}}
<p>
<strong>id</strong>: {{user.auth.twitter.id}}<br/>
<strong>token</strong>: {{user.auth.twitter.token}}<br/>
<strong>username</strong>: {{user.auth.twitter.username}}<br/>
<strong>displayName</strong>: {{user.auth.twitter.displayName}}<br/>
</p>
<a href="/twitter/unlink" class="btn btn-info">Desconectar</a>
{{else}}
<a href="/twitter/link" class="btn btn-info">Conectar ao Twitter</a>
{{/if}}
</div>
</div>
Quando iniciamos a aplicação e nos logamos com a autenticação local, percebemos esta tela:
Quando nos conectarmos pelo Twitter, ficará assim:
Agora, podemos remover um dos usuários, que a aplicação já suporta múltiplas contas. Os próximos posts, continuaremos com a atualização do código para adicionar as demais contas (Facebook e Google)
Inté.
Demais posts desta série:








Nenhum comentário:
Postar um comentário