Adicionar autenticação ao seu aplicativo web tradicional
Este guia assume que você criou um Aplicativo do tipo "Traditional web" no Console de Administração.
Seu aplicativo pode rodar no lado do servidor em vez do navegador, usando frameworks como Django, Laravel, etc. Isso é chamado de aplicativo web tradicional. Se você não encontrar um SDK adequado nesta página, provavelmente precisará integrar manualmente.
Este artigo orienta você sobre como concluir isso passo a passo. E tomamos o Express no Node.js como exemplo.
Este artigo não é apenas para Express, e se você estiver usando outros frameworks ou até mesmo outras linguagens, pode substituir @logto/js
pelo SDK principal da outra linguagem e, em seguida, ajustar algumas das etapas.
Obter código-fonte
Você pode ir ao GitHub para obter o código final deste guia.
Iniciar um projeto Express
Com express-generator
, você pode iniciar rapidamente um projeto Express.
mkdir express-logto
cd express-logto
npx express-generator
Instalar dependências
O aplicativo de demonstração precisará de 4 dependências:
- @logto/js: SDK principal do Logto para JavaScript.
- node-fetch: Código mínimo para uma API compatível com
window.fetch
no runtime do Node.js. - express-session: Um middleware de sessão, usaremos a sessão para armazenar tokens de usuário.
- js-base64: Mais um transcodificador Base64.
- npm
- Yarn
- pnpm
npm i @logto/js node-fetch@v2 express-session js-base64
yarn add @logto/js node-fetch@v2 express-session js-base64
pnpm add @logto/js node-fetch@v2 express-session js-base64
Usar sessão
Quando os usuários fazem login, eles receberão um conjunto de tokens (Tokens de acesso, Tokens de ID, Tokens de atualização) e dados de interação, e a sessão é um excelente lugar para armazená-los.
Instalamos express-session na etapa anterior, então agora vamos simplesmente adicionar o seguinte código para configurá-lo:
// app.js
const session = require('express-session');
app.use(
session({
secret: 'keyboard cat', // Altere para sua própria chave secreta
cookie: { maxAge: 86400 },
})
);
Implementar funções para autenticar usuários
Assumimos que o aplicativo está rodando em http://localhost:3000
nos trechos de código a seguir.
Nesta etapa, precisamos implementar as seguintes funções de autenticação:
getSignInUrl
: constrói e retorna uma URL completa do Servidor de Autorização Logto para a qual os usuários serão redirecionados.handleSignIn
: analisa a URL de callback após o processo de autenticação ser concluído, obtém o parâmetro de consulta de código e, em seguida, busca tokens (um token de acesso, o token de atualização e um token de ID) para concluir o processo de login.refreshTokens
: troca um novo token de acesso usando o token de atualização.
Você pode encontrar e copiar o "App Secret" na página de detalhes do aplicativo no Admin Console:
// logto.js
const {
withReservedScopes,
fetchOidcConfig,
discoveryPath,
generateSignInUri,
verifyAndParseCodeFromCallbackUri,
fetchTokenByAuthorizationCode,
fetchTokenByRefreshToken,
} = require('@logto/js');
const fetch = require('node-fetch');
const { randomFillSync, createHash } = require('crypto');
const { fromUint8Array } = require('js-base64');
const config = {
endpoint: 'https://logto.dev',
appId: 'foo',
appSecret: '<your-app-secret-copied-from-console>',
redirectUri: 'http://localhost:3000/callback', // Você pode precisar substituí-lo pelo endereço de produção do seu aplicativo
scopes: withReservedScopes().split(' '),
};
const requester = (input, init) => {
const { appId, appSecret } = config;
return fetch(input, {
...init,
headers: {
...init?.headers,
authorization: `Basic ${Buffer.from(`${appId}:${appSecret}`, 'utf8').toString('base64')}`,
},
});
};
const generateRandomString = (length = 64) => {
return fromUint8Array(randomFillSync(new Uint8Array(length)), true);
};
const generateCodeChallenge = async (codeVerifier) => {
const encodedCodeVerifier = new TextEncoder().encode(codeVerifier);
const hash = createHash('sha256');
hash.update(encodedCodeVerifier);
const codeChallenge = hash.digest();
return fromUint8Array(codeChallenge, true);
};
const getOidcConfig = async () => {
return fetchOidcConfig(new URL(discoveryPath, config.endpoint).toString(), requester);
};
exports.getSignInUrl = async () => {
const { authorizationEndpoint } = await getOidcConfig();
const codeVerifier = generateRandomString();
const codeChallenge = await generateCodeChallenge(codeVerifier);
const state = generateRandomString();
const { redirectUri, scopes, appId: clientId } = config;
const signInUri = generateSignInUri({
authorizationEndpoint,
clientId,
redirectUri: redirectUri,
codeChallenge,
state,
scopes,
});
return { redirectUri, codeVerifier, state, signInUri };
};
exports.handleSignIn = async (signInSession, callbackUri) => {
const { redirectUri, state, codeVerifier } = signInSession;
const code = verifyAndParseCodeFromCallbackUri(callbackUri, redirectUri, state);
const { appId: clientId } = config;
const { tokenEndpoint } = await getOidcConfig();
const codeTokenResponse = await fetchTokenByAuthorizationCode(
{
clientId,
tokenEndpoint,
redirectUri,
codeVerifier,
code,
},
requester
);
return codeTokenResponse;
};
exports.refreshTokens = async (refreshToken) => {
const { appId: clientId, scopes } = config;
const { tokenEndpoint } = await getOidcConfig();
const tokenResponse = await fetchTokenByRefreshToken(
{
clientId,
tokenEndpoint,
refreshToken,
scopes,
},
requester
);
return tokenResponse;
};
Fazer login
Antes de mergulharmos nos detalhes, aqui está uma visão geral rápida da experiência do usuário final. O processo de login pode ser simplificado da seguinte forma:
- Seu aplicativo invoca o método de login.
- O usuário é redirecionado para a página de login do Logto. Para aplicativos nativos, o navegador do sistema é aberto.
- O usuário faz login e é redirecionado de volta para o seu aplicativo (configurado como o URI de redirecionamento).
Sobre o login baseado em redirecionamento
- Este processo de autenticação segue o protocolo OpenID Connect (OIDC), e o Logto aplica medidas de segurança rigorosas para proteger o login do usuário.
- Se você tiver vários aplicativos, pode usar o mesmo provedor de identidade (Logto). Uma vez que o usuário faz login em um aplicativo, o Logto completará automaticamente o processo de login quando o usuário acessar outro aplicativo.
Para saber mais sobre a lógica e os benefícios do login baseado em redirecionamento, veja Experiência de login do Logto explicada.
Configurar Redirect URI
Vamos mudar para a página de detalhes do Aplicativo no Logto Console. Adicione um URI de redirecionamento http://localhost:3000/callback
e clique em "Salvar alterações".
Implementar rota de login
Antes de chamar getSignInUrl()
, certifique-se de que você configurou corretamente o URI de redirecionamento no Admin Console.
Crie uma rota no Express para fazer login:
const { getSignInUrl } = require('./logto');
app.get('/sign-in', async (req, res) => {
const { redirectUri, codeVerifier, state, signInUri } = await getSignInUrl();
req.session.signIn = { codeVerifier, state, redirectUri };
res.redirect(signInUri);
});
e uma rota para lidar com o callback:
app.get('/callback', async (req, res) => {
if (!req.session.signIn) {
res.send('Bad request.');
return;
}
const response = await handleSignIn(
req.session.signIn,
`${req.protocol}://${req.get('host')}${req.originalUrl}`
);
req.session.tokens = {
...response,
expiresAt: response.expiresIn + Date.now(),
idToken: decodeIdToken(response.idToken),
};
req.session.signIn = null;
res.redirect('/');
});
Fazer logout
TODO: link para o capítulo "sessão & cookies" na referência de usuários.
Você pode limpar tokens na sessão para fazer logout de um usuário deste aplicativo. E confira este link para ler mais sobre "fazer logout".
app.get('/sign-out', (req, res) => {
req.session.tokens = null;
res.send('Sign out successful');
});
Acessar recurso protegido
Crie um middleware chamado withAuth
para anexar um objeto auth
ao req
e verificar se um usuário está logado.
// auth.js
const { decodeIdToken } = require('@logto/js');
const { refreshTokens } = require('./logto');
const withAuth =
({ requireAuth } = { requireAuth: true }) =>
async (req, res, next) => {
if (requireAuth && !req.session.tokens) {
res.redirect('/sign-in');
return;
}
if (req.session.tokens) {
if (req.session.tokens.expiresAt >= Date.now()) {
// Token de acesso expirado, atualizar para novos tokens
try {
const response = await refreshTokens(req.session.tokens.refreshToken);
req.session.tokens = {
...response,
expiresAt: response.expiresIn + Date.now(),
idToken: decodeIdToken(response.idToken),
};
} catch {
// Falha na troca, redirecionar para login
res.redirect('/sign-in');
return;
}
}
req.auth = req.session.tokens.idToken.sub;
}
next();
};
module.exports = withAuth;
crie a página index
, mostre um link de login para convidados e um link para o perfil para usuários que já fizeram login:
router.get('/', withAuth({ requireAuth: false }), function (req, res, next) {
res.render('index', { auth: Boolean(req.auth) });
});
extends layout
block content
h1 Hello logto
if auth
p: a(href="/user") Go to profile
else
p: a(href="/sign-in") Click here to sign in
crie a página user
, para exibir userId
(sujeito
):
app.get('/user', withAuth(), (req, res, next) => {
res.render('user', { userId: req.auth });
});
extends layout
block content
h1 Hello logto
p userId: #{userId}