Conteúdo


Mastering MEAN

Gerenciando a autenticação com OAuth e Passport

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: Mastering MEAN

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:Mastering MEAN

Fique ligado em conteúdos adicionais dessa série.

O aplicativo User Group List and Information (UGLI) está começando a tomar forma satisfatoriamente. Agora você pode mostrar o conteúdo local que cria por meio das telas de CRUD que você configura em "CRUD de MEAN e UGLI com design da web responsivo." É possível também incorporar conteúdo a partir de um site externo usando os serviços desenvolvidos em "MEAN encontra Meetup.com e microdados."

O compartilhamento de informações de reunião com o público geral é uma parte importante deste projeto. Mas, como um líder do grupo de usuários, eu também desejo limitar algumas atividades a membros registrados do grupo. Por exemplo, posso optar por preservar alguma civilidade no comentário sobre nossas apresentações, desativando o acesso anônimo e requerendo um login. Portanto, nesta parte do artigo, você usará o serviço OAuth do Meetup.com para fornecer recursos de login ao aplicativo UGLI. Veja a execução de Faça download para obter o código de amostra.

Criando uma nova conta do usuário

Clicando no botão Inscreva-se (mostrado na Figura 1), os usuários de seu aplicativo podem criar uma nova conta que é armazenada localmente no MongoDB. Esta funcionalidade foi desenvolvida para seu aplicativo — nenhuma programação adicional é necessária.

Figura 1. Página de inscrição do UGLI
Screenshot of the UGLI sign-up page
Screenshot of the UGLI sign-up page

Este comportamento padrão é certamente a solução mais fácil a partir de uma perspectiva de desenvolvimento, mas deixa algo a desejar em termos de experiência do usuário. Os membros de seu grupo de usuários já possuem uma conta em Meetup.com que eles usam para RSVP para reuniões futuras. Solicitar que eles criem e mantenham um conjunto duplicado de credenciais não é apenas desagradável — é uma violação ostensiva do princípio Don't Repeat Yourself (DRY).

Por sorte, com a pilha do MEAN que você está usando, é possível estabelecer uma solução de autenticação e autorização distribuída que usa OAuth e Passport. Em outras palavras, seus usuários podem efetuar login (autenticar-se) no aplicativo UGLI com as mesmas credenciais usadas para efetuar login em Meetup.com. Mas isto não pode ser feito sem a permissão dos usuários; eles devem permitir (autorizar) o aplicativo UGLI a usar suas credenciais do Meetup.com.

Mesmo após o aplicativo UGLI ser autorizado via OAuth, as credenciais do usuário não são compartilhadas com o aplicativo autorizado. Você não armazena um conjunto duplicado de nomes de usuário e senhas localmente no UGLI. O aplicativo que se espera ser autorizado (UGLI) redireciona usuários ao provedor do OAuth (Meetup.com), onde eles fornecem suas credenciais (nome de usuário e senha). Após um usuário se autenticar com sucesso, um token de acesso é retornado ao aplicativo autorizado.

Este esquema corta uma enorme faixa de código e lógica de seu aplicativo. Você não precisa mais se preocupar com o armazenamento de senhas criptografadas em seu servidor — isso agora é um problema a ser tratado com o provedor OAuth. Da mesma forma, você não deve mais gravar algoritmos para impingir senhas fortes, lidar com senhas esquecidas, ou forçar os usuários a mudar suas senhas periodicamente.

Portanto, o OAuth fornece uma quantidade menor de senhas para seus usuários memorizarem e, significativamente, menos códigos para você gravar. Se essa não é a definição correta de um cenário de relação de benefício mútuo, eu não sei o que é.

Introduzindo OAuth e Passport

Não é mais necessário gravar algoritmos para impingir senhas fortes, ou lidar com senhas esquecidas, ou forçar usuários a mudar suas senhas periodicamente.

OAuth é um padrão aberto para autenticação e autorização distribuídas. Ele foi desenvolvido em 2006 pelo Twitter e o parceiro de negócios Ma.gnolia para facilitar a criação de widgets da área de trabalho que exibem informações de serviços autenticados. Desde então, o OAuth tem sido adotado por vários websites importantes, como Google, Facebook, Twitter, GitHub, LinkedIn, entre outros. (Consulte a Lista de provedores de serviços notáveis do OAuth.)

Passport é uma biblioteca do OAuth gravada para Node.js. Especificamente, é uma forma de middleware incorporada de maneira simples aos aplicativos Express. Mais de 140 plug-ins do Passport (chamados de estratégias) estão disponíveis, feitos sob medida para cada provedor do OAuth.

Se você abrir o arquivo package.json do aplicativo UGLI em um editor de texto (conforme mostrado na Listagem 1), poderá ver estratégias do Passport para quatro serviços principais — Facebook, Twitter, LinkedIn e Google — juntamente com uma estratégia local para armazenar credenciais diretamente no MongoDB.

Lista 1. Estratégias do Passport no package.json
"dependencies": {
    "passport": "~0.2.0",
    "passport-local": "~1.0.0",
    "passport-facebook": "~1.0.2",
    "passport-twitter": "~1.0.2",
    "passport-linkedin": "~0.1.3",
    "passport-google-Oauth": "~0.1.5"
}

Usando os exemplos existentes como uma referência, você incluirá uma sexta estratégia para incorporar o Meetup.com como um provedor do OAuth para UGLI.

Instalando a estratégia do Passport do Meetup.com

Se você visitar Autenticando-se com a API do Meetup, verá que o Meetup.com oferece serviços do OAuth. Uma procura rápida da web para estratégia do meetup.com passport.js gera um link para a biblioteca que você está procurando: passport-meetup.

Digite npm install passport-meetup --save para fazer download da biblioteca no node_modules e atualize o bloco de dependências no package.json.

Essa foi a parte fácil. Com a estratégia em vigor, seu próximo passo é incorporá-la nas páginas de inscrição e sign-in.

Incluindo um link de Meetup.com nas páginas de inscrição e sign-in

Abra public/modules/users/views/signup.client.view.html em um editor de texto. Na parte superior do arquivo, é possível ver links para os vários provedores do OAuth (conforme mostrado na Listagem 2).

Lista 2. public/modules/users/views/signup.client.view.html
<h3 class="col-md-12 text-center">Sign up using your social accounts</h3>
<div class="col-md-12 text-center">
    <a href="/auth/facebook" class="undecorated-link">
        <img src="/modules/users/img/buttons/facebook.png">
    </a>
    <a href="/auth/twitter" class="undecorated-link">
        <img src="/modules/users/img/buttons/twitter.png">
    </a>
    <a href="/auth/google" class="undecorated-link">
        <img src="/modules/users/img/buttons/google.png">
    </a>
    <a href="/auth/linkedin" class="undecorated-link">
        <img src="/modules/users/img/buttons/linkedin.png">
    </a>
</div>

Substitua os links existentes por um que aponte para seu ícone do /auth/meetup route and displays the yet-to-be-downloaded Meetup.com ainda a ser criado (conforme mostrado na Listagem 3).

Lista 3. Link para auth/meetup
<h3 class="col-md-12 text-center">Sign up using your Meetup.com account</h3>
<div class="col-md-12 text-center">
    <a href="/auth/meetup" class="undecorated-link">
        <img src="/modules/users/img/buttons/meetup.png">
    </a>
</div>

Visite a página Ícone do Meetup e salve a imagem de 128x128 pixels em public/modules/users/img/buttons/, no qual os outros ícones de mídia social estão armazenados.

Agora que a página de inscrição teve o stub efetuado, abra public/modules/users/views/signin.client.view.html em um editor de texto e ajuste-o da mesma maneira que você fez para a página de inscrição (conforme mostrado na Listagem 4).

Lista 4. public/modules/users/views/signin.client.view.html
<h3 class="col-md-12 text-center">Sign in using your Meetup.com account</h3>
<div class="col-md-12 text-center">
    <a href="/auth/meetup" class="undecorated-link">
        <img src="/modules/users/img/buttons/meetup.png">
    </a>
    </div>

Se tudo correr conforme o planejado, sua nova página de inscrição será semelhante à Figura 2. É claro que sem a rota em vigor, você receberá um erro 404 Página Não Encontrada se clicar no link. Você corrigirá isso a seguir.

Figura 2. Nova página de inscrição do UGLI
Screenshot of the new UGLI sign-up page
Screenshot of the new UGLI sign-up page

Configurando as rotas de auth/meetup do lado do servidor

A próxima etapa é criar as rotas de auth/meetup do lado do servidor. Lembre-se que toda a lógica do lado do servidor é armazenada no diretório do aplicativo; a lógica do lado do cliente é armazenada na pasta pública.

Abra app/routes/users.server.routes.js em um editor de texto. Localize o bloco de código para Facebook, copie e cole-o, substituindo facebook por meetup (conforme mostrado na Listagem 5).

Lista 5. app/routes/users.server.routes.js
// Setting the facebook Oauth routes
app.route('/auth/facebook').get(passport.authenticate('facebook', {
    scope: ['email']
}));
app.route('/auth/facebook/callback').get(users.OauthCallback('facebook'));

// Setting the meetup Oauth routes
app.route('/auth/meetup').get(passport.authenticate('meetup', {
    scope: ['email']
}));
app.route('/auth/meetup/callback').get(users.OauthCallback('meetup'));

Lembra-se dos hyperlinks para auth/meetup que você criou nas páginas de inscrição e sign-in na seção anterior? A primeira rota (auth/meetup) será acionada quando o usuário enviar uma solicitação de HTTP GET ao servidor clicando no link. O Passport tentará autenticar o usuário usando a estratégia passport-meetup . Os resultados da tentativa de login (bem-sucedida ou malsucedida) serão enviados à segunda rota de auth/meetup/callback assíncrona.

Se você clicar no link do Meetup na página de inscrição agora, receberá um Erro do Servidor 500 em vez de um 404. Isso não é exatamente uma melhoria, mas pelo menos é um progresso. A seguir: configurando a estratégia do Meetup.

Configurando a estratégia do Meetup

É possível localizar todas as estratégias do Passport no diretório config/strategies epônimo. Copie facebook.js para meetup.js e, em seguida, abra meetup.js em um editor de texto.

Como você fez na seção anterior, você percorrerá este arquivo e substituirá todas as instâncias de facebook por meetup. Mas isto é mais do que uma simples operação de localização/substituição. É necessário também fazer algumas mudanças secundárias na configuração.

Para iniciar, mude a biblioteca requerida na parte superior do arquivo da estratégia do Facebook para a do Meetup (conforme mostrado na Listagem 6).

Lista 6. config/strategies/meetup.js
/**
 * Module dependencies.
 */
var passport = require('passport'),
    url = require('url'),
    MeetupStrategy = require('passport-meetup').Strategy,
    config = require('../config'),
    users = require('../../app/controllers/users');

Em seguida, é necessário customizar o bloco de opções que é transmitido para a nova estratégia. Estes valores variam de estratégia para estratégia. A Listagem 7 mostra as opções de estratégia do Facebook, que não funcionarão para Meetup.

Lista 7. Opções do Facebook que não funcionarão para Meetup
module.exports = function() {
    // Use facebook strategy
    passport.use(new FacebookStrategy({
            clientID: config.facebook.clientID,
            clientSecret: config.facebook.clientSecret,
            callbackURL: config.facebook.callbackURL,
            passReqToCallback: true
        },

Felizmente, o módulo passport-meetup no qual você executou npm install anteriormente fornecido com o código de exemplo. Abra node_modules/passport-meetup/examples/login/app.js em um editor de texto. Procure pela chamada de função passport.use (mostrado na Listagem 8).

Lista 8. node_modules/passport-meetup/examples/login/app.js
passport.use(new MeetupStrategy({
    consumerKey: MEETUP_KEY,
    consumerSecret: MEETUP_SECRET,
    callbackURL: "http://127.0.0.1:3000/auth/meetup/callback"
  },

Copie este fragmento no meetup.js, sobrescrevendo o código do Facebook. Em seguida, mude os valores do lado direito dos dois pontos para aqueles mostrados na Listagem 9.

Lista 9. Opções de Meetup que funcionarão
passport.use(new MeetupStrategy({
    consumerKey: config.meetup.consumerKey,
    consumerSecret: config.meetup.consumerSecret,
    callbackURL: config.meetup.callbackURL,
    },

Na próxima seção, você obterá o consumerKey e consumerSecret do Meetup.com e os salvará no arquivo config.js. Mas é necessário fazer algumas mudanças adicionais no arquivo atual antes de prosseguir.

A função imediatamente após o construtor new MeetupStrategy é o manipulador de eventos que recebe a resposta do Meetup.com. Você está interessado em três partes principais da resposta: o token de acesso, o token de atualização e o perfil do usuário. (Consulte a barra lateral Tokens de acesso e tokens de atualização do OAuth para obter detalhes sobre os tokens.)

O token de acesso e o token de atualização são sequências que você transmite ao Passport sem alterar nada. Embora eles sejam cruciais para o sucesso da operação do OAuth, eles são muito maçantes de examinar sozinhos. (A Listagem 10 inclui exemplos de ambos.)

O perfil do usuário é mais interessante. Ele é um objeto JSON retornado pelo provedor do OAuth que contém informações sobre o usuário que se autenticou com êxito. Os detalhes específicos variarão de provedor do OAuth para provedor do OAuth. A Listagem 10 mostra um exemplo do perfil do usuário retornado pelo Meetup.

Lista 10. Perfil do usuário retornado a partir do provedor do OAuth do Meetup.com
{ 
  provider: 'meetup',
  id: 13848777,
  displayName: 'Scott Davis',
  _raw: '{
      "results": [{
        "status": "active",
        "link": "http:\\\/\\\/www.meetup.com\\\/members\\\/13848777",
        "photo": {
          "photo_link": "http:\\\/\\\/photos1.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/member_11849906.jpeg",
          "thumb_link": "http:\\\/\\\/photos3.meetupstatic.com\\\/photos\\\/member\\\/7\\\/4\\\/d\\\/2\\
\/thumb_11849906.jpeg",
          "photo_id": 11849906
        },
        "country": "us",
        "state": "CO",
        "city": "Denver",
        "id": 13848777,
        "joined": 1295844957000,
        "bio": "Scott Davis is the founder of ThirstyHead.com, a training and
consulting company that specializes in leading-edge technology solutions like
HTML 5, NoSQL, Groovy, and Grails.",
        "name": "Scott Davis",
        "other_services": {
          "twitter": {
            "identifier": "@scottdavis99"
          }
        }
      }]  
   }',
  _json: {
     results: [ [Object] ],
     meta: {
        link: 'https://api.meetup.com/2/members',
        total_count: 1,
        url: 'https://api.meetup.com/2/members?order=name&member_id=13848777&offset=0
&format=json&page=800',
        title: 'Meetup Members v2',
        updated: 1392763702000,
        description: 'API method for accessing members of Meetup Groups',
        method: 'Members',
     },
     accessToken: 'c7b5577bb80aab55439785cd86abcdef',
     refreshToken: '2af98db68950235a1e2519a734abcdef'
  } 
}

Como você pode ver, o Meetup retorna detalhes do usuário, tais como nome, local da residência, data em que passou a fazer parte, fotos do perfil, contas de mídias sociais vinculadas, e assim por diante.

A última coisa que você precisa fazer para concluir a customização da estratégia do Meetup é mapear os campos de perfil do Meetup de volta para o objeto User Mongoose definido em app/models/user.server.model.js. Edite o bloco restante do Facebook em config/strategies/meetup.js, conforme mostrado na Listagem 11.

Lista 11. Mapeando o perfil do usuário do OAuth para o objeto User
// Create the user OAuth profile
var providerUserProfile = {
    firstName: '',
    lastName: '',
    displayName: profile.displayName,
    email: '',
    username: profile.id,
    provider: profile.provider,
    providerIdentifierField: 'id',
    providerData: providerData
};

Se você vir campos no JSON do perfil do Meetup que gostaria de incluir no objeto User , este é o momento perfeito para fazer suas mudanças. Não se esqueça de incluir os novos campos nos formulários HTML em public/modules/users/views.

Seu config/strategies/meetup.js concluído deve ser semelhante à Listagem 12.

Lista 12. O config/strategies/meetup.js concluído
'use strict';

/**
 * Module dependencies.
 */
var passport = require('passport'),
    url = require('url'),
    MeetupStrategy = require('passport-meetup').Strategy,
    config = require('../config'),
    users = require('../../app/controllers/users');

module.exports = function() {
    // Use meetup strategy
    passport.use(new MeetupStrategy({
        consumerKey: config.meetup.clientID,
        consumerSecret: config.meetup.clientSecret,
        callbackURL: config.meetup.callbackURL,
        },
        function(req, accessToken, refreshToken, profile, done) {
            // Set the provider data and include tokens

            var providerData = profile._json;
            providerData.accessToken = accessToken;
            providerData.refreshToken = refreshToken;

            // Create the user OAuth profile
            var providerUserProfile = {
                firstName: '',
                lastName: '',
                displayName: profile.displayName,
                email: '',
                username: profile.id,
                provider: profile.provider,
                providerIdentifierField: 'id',
                providerData: providerData
            };

            // Save the user OAuth profile
            users.saveOAuthUserProfile(req, providerUserProfile, done);
        }
    ));
};

Antes de poder testar este código, é necessário fazer mais uma coisa: obter o consumerKey e consumerSecret do Meetup.

Obtendo o consumerKey e consumerSecret do Meetup

Eu passei o artigo inteiro até aqui falando sobre a autenticação do usuário. Mas, antes que os usuários possam usar o OAuth para efetuar login no UGLI, você (o desenvolvedor) deve fornecer prova de que sua organização é o que alega ser. Você faz isto fornecendo uma chave pública (a consumerKey) ao usuário. Seu aplicativo também precisa saber qual é sua chave privada (o consumerSecret).

Se você trabalhou com Infraestrutura de Chave Pública (PKI) antes, você sabe que é importante manter sua chave privada oculta e segura. Se outra pessoa descobrir sua chave privada, ela poderá se passar por sua organização. Em contrapartida, se você não compartilhar sua chave pública com os usuários, eles não poderão comprovar quem você é.

Se você for o organizador de um grupo de usuários no Meetup.com, poderá gerar o consumerKey e consumerSecret a partir da página Seus Consumidores do OAuth do Meetup (consulte a Figura 3). Eu usei HTML5 Denver User Group para o Nome do Consumidor, http://www.meetup.com/HTML5-Denver-Users-Group/ para o Website do Aplicativo e http://localhost:3000/auth/meetup/callback para o URI de Redirecionamento. Após colocar o aplicativo UGLI em produção, mudarei o Website do Aplicativo para http://html5denver.com e o URI de Redirecionamento para http://html5denver.com/auth/meetup/callback.

Figura 3. Gerando o consumerKey e consumerSecret para o HTML5 Denver
Screenshot of generating the consumerKey and consumerSecret for HTML5 Denver
Screenshot of generating the consumerKey and consumerSecret for HTML5 Denver

Se você não executar um grupo de usuários no Meetup.com, sua conta não estará autorizada para gerar chaves OAuth em nome de um grupo. No entanto, ainda é possível gerar tokens para uma de suas outras contas de mídia social e ajustar as etapas neste artigo de forma adequada. Consulte Implementando o Sign in com o Twitter para gerar chaves do aplicativo para sua conta do Twitter ou Tokens de Acesso para usar sua conta do Facebook. Uma procura da web rápida em seu website de mídia social chaves Oauth deve produzir instruções passo a passo.

Incluindo as chaves OAuth da organização em seu aplicativo

Após ter as duas chaves (pública e privada), você as incluirá em seu aplicativo via variáveis de ambiente — muito semelhante a como você mudou a PORT em "Testar um aplicativ MEAN."

Lembre-se que você pode configurar variáveis que mudam com base no modo como você está executando em: desenvolvimento, produçãoou teste. Os valores específicos do ambiente são armazenados em config/env. Abra config/env/development.js em um editor de texto. Copie/cole o bloco do Facebook e o ajuste de acordo para Meetup (conforme mostrado na Listagem 13). Certifique-se de que os nomes de atributos aqui correspondam aos nomes de atributos usados na chamada de função passport.use em config/strategies/meetup.js.

Lista 13. config/env/development.js
'use strict';

module.exports = {
    db: 'mongodb://localhost/test-dev',
    app: {
        title: 'HTML5 Denver'
    },

    meetup: {
        consumerKey: process.env.MEETUP_KEY || 'APP_ID',
        consumerSecret: process.env.MEETUP_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/meetup/callback'
    },
    facebook: {
        clientID: process.env.FACEBOOK_ID || 'APP_ID',
        clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/facebook/callback'
    },
    twitter: {
        clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
        clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
        callbackURL: 'http://localhost:3000/auth/twitter/callback'
    },
    google: {
        clientID: process.env.GOOGLE_ID || 'APP_ID',
        clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/google/callback'
    },
    linkedin: {
        clientID: process.env.LINKEDIN_ID || 'APP_ID',
        clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
        callbackURL: 'http://localhost:3000/auth/linkedin/callback'
    }
};

Você poderia substituir APP_ID e APP_SECRET por valores codificados permanentemente para o consumerKey e consumerSecret que você recuperou na seção anterior. Mas uma solução mais segura é fornecer estes valores para o aplicativo UGLI via variáveis de ambiente. Para iniciar seu aplicativo com o consumerKey e consumerSecret de sua organização, digite:

MEETUP_KEY=l75fkklhurkack36eelfhhfhjc MEETUP_SECRET=abcdeg316jd3ni43f21u1abcde NODE_ENV=development grunt

Não se esqueça de fazer ajustes semelhantes em config/env/production.js antes de entrar em execução. E, se você criou uma conta do usuário anteriormente, certifique-se de excluí-la do banco de dados html5-denver-dev no MongoDB para que você possa percorrer todo o processo de criação de nova conta novamente.

Conclusão

Larry Wall (criador da linguagem de programação Perl) disse: "Coisas fáceis devem ser fáceis, e coisas difíceis devem ser possíveis." Espero que este sentimento resuma bem sua experiência em aplicar o OAuth e o Passport para uso do Meetup.com com relação às suas necessidades de autenticação e autorização distribuídas.

Na próxima parte do artigo Mastering MEAN , analisarei a infraestrutura de teste criada para a pilha do MEAN. Você aprenderá sobre Mocha para teste do lado do servidor, Jasmine para teste do lado do cliente e Karma para executar seus testes em vários navegadores. Até lá, divirta-se dominando o MEAN.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=1026540
ArticleTitle=Mastering MEAN: Gerenciando a autenticação com OAuth e Passport
publish-date=02032016