Atlassian OAuth 2.0 (3LO)
Authorization Code Exchange para web e mobile — setup Atlassian OAuth 2.0 (3LO), referência da API e exemplos de frontend.
Visão geral
O login Atlassian é opcional e controlado por ATLASSIAN_AUTH_ENABLED. Quando desligado, os endpoints Atlassian respondem 503 Service Unavailable. JWTs nunca vão na URL de redirect; a API emite um exchangeCode de uso único (Redis, 60 segundos). O mesmo POST /auth/exchange é compartilhado com os demais provedores sociais.
- • Ativado por feature flag no ambiente
- • Strategy passport-oauth2 customizada + AtlassianAuthGuard
- • Perfil obtido via GET https://api.atlassian.com/me após troca do token
- • Params de authorize incluem audience=api.atlassian.com e prompt=consent
- • Allowlist de redirect evita open redirects
- • Mesmo formato de tokens do POST /auth/login
Atlassian Developer Console
Crie um app em https://developer.atlassian.com/console/myapps/ → Create → OAuth 2.0 (3LO). Defina a Callback URL igual a ATLASSIAN_CALLBACK_URL. Adicione a User Identity API e o scope read:me.
- Abra https://developer.atlassian.com/console/myapps/ e clique em Create → OAuth 2.0 (3LO)
- Defina a Callback URL (ex.: http://localhost:3000/api/v1/auth/atlassian/callback)
- Adicione a User Identity API e o scope read:me
- Copie Client ID e Client Secret para o .env
- Garanta que ATLASSIAN_CALLBACK_URL corresponda à Callback URL registrada no console Atlassian
Variáveis de ambiente
Carregadas do .env via src/config/atlassian-oauth.config.ts (namespace atlassianOAuth no ConfigService). Veja .env.example.
ATLASSIAN_AUTH_ENABLED=true ATLASSIAN_CLIENT_ID=your-atlassian-client-id ATLASSIAN_CLIENT_SECRET=your-atlassian-client-secret ATLASSIAN_CALLBACK_URL=http://localhost:3000/api/v1/auth/atlassian/callback ATLASSIAN_REDIRECT_ALLOWLIST=myapp://success,http://localhost:5173/auth/callback ATLASSIAN_OAUTH_DEFAULT_ROLES=
Roles padrão para novos usuários Atlassian
Novos usuários Atlassian recebem roles: [] por padrão (igual ao register). Defina ATLASSIAN_OAUTH_DEFAULT_ROLES no .env como lista separada por vírgula (ex.: user ou user,manager). Usuários existentes vinculados por e-mail mantêm as roles atuais.
Sequência
- Cliente abre GET /api/v1/auth/atlassian?redirect=<URL na allowlist>
- Usuário consente no Atlassian; o browser chama GET /api/v1/auth/atlassian/callback
- API chama GET https://api.atlassian.com/me, faz upsert do usuário, grava exchangeCode no Redis e redireciona para redirect?code=...
- Cliente faz POST { code } em /api/v1/auth/exchange e recebe os JWTs
Referência da API
1. Iniciar OAuth
Abre o consentimento do Atlassian em https://auth.atlassian.com/authorize com audience=api.atlassian.com e prompt=consent. O query redirect deve bater com um prefixo de ATLASSIAN_REDIRECT_ALLOWLIST (ou omita para usar a primeira entrada). Scope: read:me.
GET /api/v1/auth/atlassian?redirect=http%3A%2F%2Flocalhost%3A5173%2Fauth%2Fcallback
400 redirect inválido · 503 feature desligada
2. Callback
Tratado pela API. A troca de token usa https://auth.atlassian.com/oauth/token. Redireciona para o app com ?code= (ou &code= se a URL já tiver query).
GET /api/v1/auth/atlassian/callback?code=ATLASSIAN_AUTH_CODE&state=... → 302 Location: http://localhost:5173/auth/callback?code=EXCHANGE_CODE
3. Trocar código
Endpoint público compartilhado com os demais provedores sociais. Consome o código (uso único) e retorna o mesmo formato do login.
POST /api/v1/auth/exchange
Content-Type: application/json
{
"code": "EXCHANGE_CODE"
}{
"user": {
"id": "...",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"roles": [],
"atlassianId": "712020:abc123",
"isActive": true
},
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}401 código inválido/expirado/usado · 503 auth social desligada
Frontend — Web (SPA)
Fluxo típico: o botão inicia OAuth na mesma janela (ou popup); a rota de callback no frontend lê code da query e chama exchange.
const API = 'http://localhost:3000/api/v1';
const redirect = encodeURIComponent(
'http://localhost:5173/auth/callback'
);
// Redirect de página inteira: Nest → Atlassian → de volta à SPA
window.location.href = `${API}/auth/atlassian?redirect=${redirect}`;// /auth/callback na SPA
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
if (!code) throw new Error('Missing exchange code');
const res = await fetch('http://localhost:3000/api/v1/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
if (!res.ok) throw new Error(await res.text());
const { user, accessToken, refreshToken } = await res.json();
// persista os tokens como sua app exigir
sessionStorage.setItem('accessToken', accessToken);
console.log('signed in', user.email);- • Inclua a URL de callback do frontend em ATLASSIAN_REDIRECT_ALLOWLIST (ex.: http://localhost:5173/auth/callback)
- • ATLASSIAN_CALLBACK_URL aponta para a API Nest, não para a SPA
- • Armazene accessToken / refreshToken com segurança
Frontend — Mobile (deep link)
Use um scheme customizado (ou universal link) na allowlist. Após o Atlassian, a API redireciona para myapp://success?code=.... O app abre, lê o código e chama exchange.
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
const API = 'https://api.example.com/api/v1';
const redirect = encodeURIComponent('myapp://success');
async function signInWithAtlassian() {
const url = `${API}/auth/atlassian?redirect=${redirect}`;
await WebBrowser.openAuthSessionAsync(url, 'myapp://success');
}import * as Linking from 'expo-linking';
async function exchangeFromUrl(url) {
const { queryParams } = Linking.parse(url);
const code = queryParams?.code;
if (!code) return;
const res = await fetch(`${API}/auth/exchange`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
const data = await res.json();
// data.accessToken, data.refreshToken, data.user
return data;
}
Linking.addEventListener('url', ({ url }) => {
exchangeFromUrl(url);
});- • Registre o scheme no SO / Expo app.json
- • Prefira Linking.addEventListener('url', ...) e getInitialURL()
- • Faça o exchange via HTTPS na base URL da API
Notas de segurança
- • URLs de redirect devem casar com prefixos de ATLASSIAN_REDIRECT_ALLOWLIST
- • Códigos de exchange são de uso único e expiram em 60 segundos (Redis)
- • Usuários locais existentes são vinculados pelo e-mail verificado do Atlassian (atlassianId; roles inalteradas)
- • Usuários só-Atlassian têm password null — login/change-password com senha falham até haver senha
- • E-mail é obrigatório via api.atlassian.com/me; o login falha se o Atlassian não devolver e-mail (scope read:me)
Modelo de usuário
Campos da entity/interface usados pelo login Atlassian:
- • atlassianId — account id Atlassian único e nullable (account_id do /me)
- • password — nullable para contas só OAuth
- • isActive — true para novos usuários Atlassian
- • roles — de ATLASSIAN_OAUTH_DEFAULT_ROLES no env (padrão [])
Relacionados
- Hub de Autenticação Social
- Guia Google OAuth2
- Guia Facebook OAuth2
- Guia X / Twitter OAuth2
- Guia GitHub OAuth2
- Guia Figma OAuth2
- Guia LinkedIn OpenID Connect
- Guia Slack OpenID Connect
- Guia GitLab OAuth2
- Guia Bitbucket OAuth2
- Guia Discord OAuth2
- Guia Twitch OAuth2
- Guia Steam OpenID 2.0
- Guia Reddit OAuth2
- Auth & JWT