Bitbucket

Bitbucket OAuth2

Authorization Code Exchange para web e mobile — setup de OAuth consumer Bitbucket, referência da API e exemplos de frontend.

Visão geral

O login Bitbucket é opcional e controlado por BITBUCKET_AUTH_ENABLED. Quando desligado, os endpoints Bitbucket 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 + BitbucketAuthGuard
  • • Perfil obtido via GET https://api.bitbucket.org/2.0/user e GET https://api.bitbucket.org/2.0/user/emails após troca do token
  • • Scopes account e email para perfil e e-mail principal
  • • Allowlist de redirect evita open redirects
  • • Mesmo formato de tokens do POST /auth/login

OAuth consumers Bitbucket

Crie um OAuth consumer em Bitbucket → Workspace settings → OAuth consumers (ou Personal settings → OAuth consumers, também listado como Settings → OAuth consumers / Apps and features). Defina o Callback URL igual a BITBUCKET_CALLBACK_URL. Habilite os scopes account e email.

  1. Abra Bitbucket → Workspace settings → OAuth consumers (ou Personal settings → OAuth consumers / Apps and features)
  2. Clique em Add consumer
  3. Defina o Callback URL (ex.: http://localhost:3000/api/v1/auth/bitbucket/callback)
  4. Habilite os scopes account e email
  5. Copie Key e Secret para o .env
  6. Garanta que BITBUCKET_CALLBACK_URL corresponda ao Callback URL registrado no Bitbucket

Variáveis de ambiente

Carregadas do .env via src/config/bitbucket-oauth.config.ts (namespace bitbucketOAuth no ConfigService). Veja .env.example.

Environment.env
BITBUCKET_AUTH_ENABLED=true
BITBUCKET_CLIENT_ID=your-bitbucket-consumer-key
BITBUCKET_CLIENT_SECRET=your-bitbucket-consumer-secret
BITBUCKET_CALLBACK_URL=http://localhost:3000/api/v1/auth/bitbucket/callback
BITBUCKET_REDIRECT_ALLOWLIST=myapp://success,http://localhost:5173/auth/callback
BITBUCKET_OAUTH_DEFAULT_ROLES=

Roles padrão para novos usuários Bitbucket

Novos usuários Bitbucket recebem roles: [] por padrão (igual ao register). Defina BITBUCKET_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

  1. Cliente abre GET /api/v1/auth/bitbucket?redirect=<URL na allowlist>
  2. Usuário consente no Bitbucket; o browser chama GET /api/v1/auth/bitbucket/callback
  3. API chama GET https://api.bitbucket.org/2.0/user e /user/emails, faz upsert do usuário, grava exchangeCode no Redis e redireciona para redirect?code=...
  4. Cliente faz POST { code } em /api/v1/auth/exchange e recebe os JWTs

Referência da API

1. Iniciar OAuth

Abre o consentimento do Bitbucket em https://bitbucket.org/site/oauth2/authorize. O query redirect deve bater com um prefixo de BITBUCKET_REDIRECT_ALLOWLIST (ou omita para usar a primeira entrada). Scopes: account email.

HTTPGET /api/v1/auth/bitbucket
GET /api/v1/auth/bitbucket?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://bitbucket.org/site/oauth2/access_token. Redireciona para o app com ?code= (ou &code= se a URL já tiver query).

HTTPGET /api/v1/auth/bitbucket/callback
GET /api/v1/auth/bitbucket/callback?code=BITBUCKET_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.

HTTP RequestPOST /api/v1/auth/exchange
POST /api/v1/auth/exchange
Content-Type: application/json

{
  "code": "EXCHANGE_CODE"
}
HTTP Response200 OK
{
  "user": {
    "id": "...",
    "email": "[email protected]",
    "firstName": "Jane",
    "lastName": "Doe",
    "roles": [],
    "bitbucketId": "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}",
    "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.

JavaScriptIniciar login Bitbucket
const API = 'http://localhost:3000/api/v1';
const redirect = encodeURIComponent(
  'http://localhost:5173/auth/callback'
);

// Redirect de página inteira: Nest → Bitbucket → de volta à SPA
window.location.href = `${API}/auth/bitbucket?redirect=${redirect}`;
JavaScriptPágina de callback /auth/callback
// /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 BITBUCKET_REDIRECT_ALLOWLIST (ex.: http://localhost:5173/auth/callback)
  • • BITBUCKET_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 Bitbucket, a API redireciona para myapp://success?code=.... O app abre, lê o código e chama exchange.

React NativeAbrir OAuth no browser
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 signInWithBitbucket() {
  const url = `${API}/auth/bitbucket?redirect=${redirect}`;
  await WebBrowser.openAuthSessionAsync(url, 'myapp://success');
}
React NativeDeep link + exchange
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 BITBUCKET_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 Bitbucket (bitbucketId; roles inalteradas)
  • • Usuários só-Bitbucket têm password null — login/change-password com senha falham até haver senha
  • • E-mail é obrigatório via api.bitbucket.org/2.0/user/emails; o login falha se o Bitbucket não devolver e-mail principal ou confirmado (scopes account e email)

Modelo de usuário

Campos da entity/interface usados pelo login Bitbucket:

  • • bitbucketId — UUID de usuário Bitbucket único e nullable (uuid do /2.0/user)
  • • password — nullable para contas só OAuth
  • • isActive — true para novos usuários Bitbucket
  • • roles — de BITBUCKET_OAUTH_DEFAULT_ROLES no env (padrão [])

Relacionados