bibliotecas de autenticação de usuário para node.js?

274

Existem bibliotecas de autenticação de usuário existentes para node.js? Em particular, estou procurando algo que possa fazer autenticação de senha para um usuário (usando um banco de dados de autenticação de back-end personalizado) e associe esse usuário a uma sessão.

Antes de escrever uma biblioteca de autenticação, imaginei que as pessoas sabiam das bibliotecas existentes. Não foi possível encontrar nada óbvio através de uma pesquisa no google.

-Shreyas

shreddd
fonte
Para pesquisa: Algo equivalente a omniauth(trilhos) ou python social-auth. Os usuários de PHP (e outras linguagens comuns de servidores da web) devem se sentir à vontade para adicionar seus equivalentes também.
forivall

Respostas:

233

Se você estiver procurando uma estrutura de autenticação para o Connect ou Express, vale a pena investigar o Passport: https://github.com/jaredhanson/passport

(Divulgação: eu sou o desenvolvedor do Passport)

Desenvolvi o Passport depois de investigar o connect-auth e everyauth. Embora os dois sejam ótimos módulos, eles não atendiam às minhas necessidades. Eu queria algo que fosse mais leve e discreto.

O Passport é dividido em módulos separados, para que você possa usar apenas o que precisa (OAuth, apenas se necessário). O Passport também não monta nenhuma rota em seu aplicativo, oferecendo a flexibilidade de decidir quando e onde você deseja autenticação e ganchos para controlar o que acontece quando a autenticação é bem-sucedida ou falha.

Por exemplo, aqui está o processo de duas etapas para configurar a autenticação baseada em formulário (nome de usuário e senha):

passport.use(new LocalStrategy(
  function(username, password, done) {
    // Find the user from your DB (MongoDB, CouchDB, other...)
    User.findOne({ username: username, password: password }, function (err, user) {
      done(err, user);
    });
  }
));

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    // Authentication successful. Redirect home.
    res.redirect('/');
  });

Estratégias adicionais estão disponíveis para autenticação via Facebook, Twitter, etc. Estratégias personalizadas podem ser conectadas, se necessário.

Jared Hanson
fonte
Entre todos os pacotes de autenticação para o nó, selecionei o passaporte. É bem documentado, fácil de usar e suporta mais estratégias.
Tech-man #
No momento, estou usando passaporte para um protótipo, e não o recomendo porque parece não ser mantido e o design não é muito bom. Por exemplo, ele obriga a usar o connect-flash quando ele pode simplesmente usar req.session.messages, e o passaporte-google anunciado no site está desatualizado, pois usa o Google OpenId que está obsoleto e não há link para o passaporte. google-oauth que deve substituí-lo. Além disso, esta é a assinatura de um retorno de chamada após a autenticação: done(null,false,{ message:'Incorrect username.' })é terrível, pois não sabemos quais são todos esses parâmetros.
Eloone 21/09
1
@eloone Preciso atualizar os documentos para apontar para os novos métodos de autenticação que o Google agora prefere. Como você mencionou, existe suporte para eles e eles funcionam bem. Quanto às questões de design, o passaporte não o força a usar o connect-flash, e os argumentos mencionados estão documentados no guia. Se você precisar de ajuda para entender, há fóruns em que as pessoas podem ajudar e responder suas perguntas.
Jared Hanson
Não é à toa - mas acabei de conectar o Passport (usei o exemplo fornecido). Super fácil! Sei que faz alguns anos desde os comentários mais recentes. Eu recomendaria que alguém desse uma olhada.
terary 19/11/19
89

Sessão + Se

Eu acho que o motivo de você não encontrar muitas boas bibliotecas é que o uso de uma biblioteca para autenticação é mais do que modificado.

O que você está procurando é apenas um fichário de sessão :) Uma sessão com:

if login and user == xxx and pwd == xxx 
   then store an authenticated=true into the session 
if logout destroy session

é isso aí.


Discordo da sua conclusão de que o plugin connect-auth é o caminho a seguir.

Também estou usando o connect, mas não uso o connect-auth por dois motivos:

  1. O IMHO quebra o connect-auth, a arquitetura muito poderosa e fácil de ler do anel de cebola do connect. Um não-go - minha opinião :). Você pode encontrar um artigo muito bom e breve sobre como funciona a conexão e a idéia do anel de cebola aqui .

  2. Se você - como está escrito - deseja apenas usar um login básico ou http com banco de dados ou arquivo. A autenticação de conexão é muito grande. É mais para coisas como OAuth 1.0, OAuth 2.0 & Co


Uma autenticação muito simples com o connect

(Está completo. Basta executá-lo para teste, mas se você quiser usá-lo em produção, certifique-se de usar https). você muda um estado :)

var connect = require('connect');
var urlparser = require('url');

var authCheck = function (req, res, next) {
    url = req.urlp = urlparser.parse(req.url, true);

    // ####
    // Logout
    if ( url.pathname == "/logout" ) {
      req.session.destroy();
    }

    // ####
    // Is User already validated?
    if (req.session && req.session.auth == true) {
      next(); // stop here and pass to the next onion ring of connect
      return;
    }

    // ########
    // Auth - Replace this example with your Database, Auth-File or other things
    // If Database, you need a Async callback...
    if ( url.pathname == "/login" && 
         url.query.name == "max" && 
         url.query.pwd == "herewego"  ) {
      req.session.auth = true;
      next();
      return;
    }

    // ####
    // This user is not authorized. Stop talking to him.
    res.writeHead(403);
    res.end('Sorry you are not authorized.\n\nFor a login use: /login?name=max&pwd=herewego');
    return;
}

var helloWorldContent = function (req, res, next) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('authorized. Walk around :) or use /logout to leave\n\nYou are currently at '+req.urlp.pathname);
}

var server = connect.createServer(
      connect.logger({ format: ':method :url' }),
      connect.cookieParser(),
      connect.session({ secret: 'foobar' }),
      connect.bodyParser(),
      authCheck,
      helloWorldContent
);

server.listen(3000);

NOTA

Eu escrevi essa declaração há mais de um ano e atualmente não tenho projetos de nós ativos. Portanto, pode haver alterações de API no Express. Por favor, adicione um comentário se eu devo mudar alguma coisa.

nivoc
fonte
Por que o connect-auth quebra o padrão de cebola / camadas? é porque não usa next ()? Poderia?
jpstrikesback
3
Sim. Ele deve usar next () porque essa é a ideia por trás do connect. O Connect possui uma arquitetura de camada / forma de estrutura de código. E toda camada tem o poder de interromper a execução da solicitação, não chamando next (). Se estivermos falando sobre autenticação: Uma camada de autenticação verificará se o usuário tem as permissões corretas. Se estiver tudo bem, a camada chama next (). Caso contrário, essa camada de autenticação gera um erro e não chama next ().
Matthias
cara, isso é exatamente o que eu estava procurando. connect-auth estava me dando um pouco de indigestão. Acabei de acessar meu aplicativo pela primeira vez. Muito obrigado.
Andy Ray
7
Isso ainda não ajuda a responder como se conectar a um back-end de banco de dados (de preferência com senhas criptografadas). Agradeço o seu comentário de que esta biblioteca é excessivamente projetada, mas certamente há uma que não é. Além disso, se eu quisesse escrever meu próprio sistema de autenticação, teria usado o Struts em Java. Assim como o OP, quero saber quais plugins farão isso por mim em 1 linha de código.
21411 hjrrixski
4
ótima resposta Nivoc. Não funciona com as versões mais recentes do connect tho. Eu tive que mudar ... cookieDecoder () -> cookieParser () e bodyDecoder () -> bodyParser () e remover a próxima chamada () da função helloWorldContent, pois estava recebendo um erro 'Não é possível definir cabeçalhos depois que eles são enviados '
Michael Dausmann
26

Parece que o plug-in connect-auth para o middleware connect é exatamente o que eu preciso: http://wiki.github.com/ciaranj/connect-auth/creating-a-form-based-strategy

Estou usando o express [ http://expressjs.com ] para que o plug-in de conexão se encaixe muito bem, pois o express é subclassificado (ok - prototipado) do connect

shreddd
fonte
1
ei, você tem um exemplo do que você fez? simplesmente exigir connect-auth e chamar ".authenticate" em "req" retornará "TypeError: Object # não possui o método 'authenticate'" para mim.
Misha Reyzlin 12/09/10
1
IMHO Este plugin é muito pesado para autenticação simples por http
Matthias
E este plugin trabalha contra a arquitetura anel de cebola connect
Matthias
14

Eu estava basicamente procurando a mesma coisa. Especificamente, eu queria o seguinte:

  1. Para usar o express.js, que envolve o recurso de middleware do Connect
  2. Autenticação "baseada em formulário"
  3. Controle granular sobre quais rotas são autenticadas
  4. Um back-end de banco de dados para usuários / senhas
  5. Usar sessões

O que acabei fazendo foi criar minha própria função de middleware, check_authque passo como argumento para cada rota que quero que seja autenticada. check_authapenas verifica a sessão e, se o usuário não está logado, redireciona-o para a página de login, da seguinte forma:

function check_auth(req, res, next) {

  //  if the user isn't logged in, redirect them to a login page
  if(!req.session.login) {
    res.redirect("/login");
    return; // the buck stops here... we do not call next(), because
            // we don't want to proceed; instead we want to show a login page
  }

  //  the user is logged in, so call next()
  next();
}

Então, para cada rota, garanto que essa função seja passada como middleware. Por exemplo:

app.get('/tasks', check_auth, function(req, res) {
    // snip
});

Finalmente, precisamos realmente lidar com o processo de login. Isto é direto:

app.get('/login', function(req, res) {
  res.render("login", {layout:false});
});

app.post('/login', function(req, res) {

  // here, I'm using mongoose.js to search for the user in mongodb
  var user_query = UserModel.findOne({email:req.body.email}, function(err, user){
    if(err) {
      res.render("login", {layout:false, locals:{ error:err } });
      return;
    }

    if(!user || user.password != req.body.password) {
      res.render("login",
        {layout:false,
          locals:{ error:"Invalid login!", email:req.body.email }
        }
      );
    } else {
      // successful login; store the session info
      req.session.login = req.body.email;
      res.redirect("/");
    }
  });
});

De qualquer forma, essa abordagem foi projetada principalmente para ser flexível e simples. Tenho certeza que existem várias maneiras de melhorá-lo. Se você tiver algum, eu gostaria muito do seu feedback.

EDIT: Este é um exemplo simplificado. Em um sistema de produção, você nunca deseja armazenar e comparar senhas em texto sem formatação. Como comenta um comentarista, existem bibliotecas que podem ajudar a gerenciar a segurança da senha.

Tom
fonte
2
isso é bom, exceto que você deve usar o bcrypt para armazenar a senha (não o texto sem formatação em db). Há um bom post aqui sobre isso: devsmash.com/blog/…
chovy
13

Também ter um olhar para everyauth se você quiser / integração de login rede social de terceiros.

Peter Lyons
fonte
7

Aqui está um código para autenticação básica de um dos meus projetos. Eu o uso no CouchDB com um cache de dados de autenticação adicional, mas retirei esse código.

Coloque um método de autenticação em torno de você solicitando a manipulação e forneça um segundo retorno de chamada para autenticação sem êxito. O retorno de chamada com êxito obterá o nome de usuário como um parâmetro adicional. Não se esqueça de lidar corretamente com solicitações com credenciais ausentes ou erradas no retorno de chamada com falha:

/**
 * Authenticate a request against this authentication instance.
 * 
 * @param request
 * @param failureCallback
 * @param successCallback
 * @return
 */
Auth.prototype.authenticate = function(request, failureCallback, successCallback)
{
    var requestUsername = "";
    var requestPassword = "";
    if (!request.headers['authorization'])
    {
        failureCallback();
    }
    else
    {
        var auth = this._decodeBase64(request.headers['authorization']);
        if (auth)
        {
            requestUsername = auth.username;
            requestPassword = auth.password;
        }
        else
        {
            failureCallback();
        }
    }


    //TODO: Query your database (don't forget to do so async)


    db.query( function(result)
    {
        if (result.username == requestUsername && result.password == requestPassword)
        {
            successCallback(requestUsername);
        }
        else
        {
            failureCallback();
        }
    });

};


/**
 * Internal method for extracting username and password out of a Basic
 * Authentication header field.
 * 
 * @param headerValue
 * @return
 */
Auth.prototype._decodeBase64 = function(headerValue)
{
    var value;
    if (value = headerValue.match("^Basic\\s([A-Za-z0-9+/=]+)$"))
    {
        var auth = (new Buffer(value[1] || "", "base64")).toString("ascii");
        return {
            username : auth.slice(0, auth.indexOf(':')),
            password : auth.slice(auth.indexOf(':') + 1, auth.length)
        };
    }
    else
    {
        return null;
    }

};
b_erb
fonte
Eu queria evitar a autenticação básica em favor da autenticação baseada em formulário. Esta é definitivamente uma solução elegante para o problema básico de autenticação. Eu acho que pode ter encontrado um bom quadro auth embora (connect-auth - fica no topo da connectjs)
shreddd
4

Uma abordagem diferente da autenticação é o Passwordless, um módulo de autenticação baseado em token que expressa o que contorna o problema inerente às senhas [1]. É rápido de implementar, não requer muitos formulários e oferece melhor segurança para o usuário médio (divulgação completa: sou o autor).

[1]: as senhas são obsoletas

florian
fonte
3

Alguns anos se passaram e eu gostaria de apresentar minha solução de autenticação para o Express. Chama-se Lockit . Você pode encontrar o projeto no GitHub e uma breve introdução no meu blog .

Então, quais são as diferenças para as soluções existentes?

  • fácil de usar: configurar o DB, npm instalar, require('lockit'), lockit(app), feito
  • rotas já incorporadas (/ inscrição, / login, / esqueceu a senha, etc.)
  • visualizações já incorporadas (com base no Bootstrap, mas você pode usar facilmente suas próprias visualizações)
  • suporta comunicação JSON para seus aplicativos de página única AngularJS / Ember.js
  • NÃO suporta OAuth e OpenID. Somente usernamee password.
  • ele funciona com vários bancos de dados (CouchDB, MongoDB, SQL) prontos para uso
  • tem testes (não encontrei nenhum teste para Drywall)
  • é mantido ativamente (comparado a tudo)
  • processo de verificação de e-mail e senha esquecida (envie e-mail com token, não suportado pelo Passport)
  • modularidade: use apenas o que você precisa
  • flexibilidade: personalize tudo

Veja os exemplos .

zemirco
fonte
2

Existe um projeto chamado Drywall que implementa um sistema de login do usuário com o Passport e também possui um painel de gerenciamento de usuários. Se você está procurando um sistema de gerenciamento e autenticação de usuário completo, semelhante a algo que o Django possui, mas o Node.js, é isso. Eu achei que era um bom ponto de partida para criar um aplicativo de nó que exigisse um sistema de autenticação e gerenciamento de usuários. Veja a resposta de Jared Hanson para obter informações sobre como o Passport funciona.

lk145
fonte
1

Exemplo simples e rápido usando mongo, para uma API que fornece autenticação de usuário para, por exemplo, cliente Angular

no app.js

var express = require('express');
var MongoStore = require('connect-mongo')(express);

// ...

app.use(express.cookieParser());
// obviously change db settings to suit
app.use(express.session({
    secret: 'blah1234',
    store: new MongoStore({
        db: 'dbname',
        host: 'localhost',
        port: 27017
    })
}));

app.use(app.router);

para sua rota, algo como isto:

// (mongo connection stuff)

exports.login = function(req, res) {

    var email = req.body.email;
    // use bcrypt in production for password hashing
    var password = req.body.password;

    db.collection('users', function(err, collection) {
        collection.findOne({'email': email, 'password': password}, function(err, user) {
            if (err) {
                res.send(500);
            } else {
                if(user !== null) {
                    req.session.user = user;
                    res.send(200);
                } else {
                    res.send(401);
                }
            }
        });
    });
};

Em suas rotas que exigem autenticação, basta verificar a sessão do usuário:

if (!req.session.user) {
    res.send(403);
}
Senhor P
fonte
0

Aqui está uma nova biblioteca de autenticação que usa tokens com registro de data e hora. Os tokens podem ser enviados por email ou texto para os usuários sem a necessidade de armazená-los em um banco de dados. Pode ser usado para autenticação sem senha ou para autenticação de dois fatores.

https://github.com/vote539/easy-no-password

Divulgação: eu sou o desenvolvedor desta biblioteca.

sffc
fonte
0

Se você precisar de autenticação com o SSO (Logon único) com a conta de usuário do Microsoft Windows. Você pode tentar https://github.com/jlguenego/node-expose-sspi .

Ele fornecerá um req.ssoobjeto que contém todas as informações do usuário do cliente (login, nome para exibição, sid, grupos).

const express = require("express");
const { sso, sspi } = require("node-expose-sspi");

sso.config.debug = false;

const app = express();

app.use(sso.auth());

app.use((req, res, next) => {
  res.json({
    sso: req.sso
  });
});

app.listen(3000, () => console.log("Server started on port 3000"));

Isenção de responsabilidade: eu sou o autor do node-expose-sspi.

jlguenego
fonte
0

sweet-auth

Um módulo de autenticação de usuário leve e com configuração zero. Ele não precisa de um banco de dados separado.

https://www.npmjs.com/package/sweet-auth

É simples como:

app.get('/private-page', (req, res) => {

    if (req.user.isAuthorized) {
        // user is logged in! send the requested page
        // you can access req.user.email
    }
    else {
        // user not logged in. redirect to login page
    }
})
Naveen
fonte