Atos Workspace
O produto da Atos para quem constrói sistemas com a Conta Atos. Hoje ele traz o Login Atos para os seus sistemas: o “Entrar com Atos”, um login pronto para o seu site, aplicativo ou servidor, em que a pessoa entra com a Conta Atos dela, do mesmo jeito que entra com o Google. E os Formulários (o formulário de contato do seu site, entregue no seu e-mail Atos), a página de status do seu sistema e a página de links (atos.dev.br/@nome). Ativar é gratuito: é aceitar os termos do Workspace. O Pro, opcional, aumenta os limites.
O que é o Atos Workspace
Conta Atos e Workspace
A Conta Atos é a identidade e a carteira de cada pessoa: com ela, ela entra, assina e paga. Quem só usa um sistema precisa só da conta. O Workspace é o que você ativa, na mesma conta, para construir.
O que inclui hoje
O Login Atos para os seus sistemas, em Atos Workspace › Integrações (5 acessos por aplicativo; quantos aplicativos e pessoas, pelo plano), com os avisos ao seu sistema quando o acesso das pessoas muda e o registro de entradas de cada aplicativo, com filtro.
A página de status: endereços do seu sistema conferidos pela Atos e publicados em atos.dev.br/status/<nome>. Como funciona.
Os Formulários: o formulário de contato de um site estático, entregue no destino do seu e-mail Atos, em Atos Workspace › Formulários. Pedem o e-mail Atos com o destino confirmado, como as Integrações. Como usar.
A página de links: atos.dev.br/@nome, com o seu nome, uma frase, uma foto e os seus links, gerada pela Atos. Como é.
O e-mail Atos (em preparação na Atos): um endereço @atos.dev.br que encaminha para o seu e-mail (não é caixa de entrada) e que também entra na sua Conta Atos, com a mesma senha. Cadastrar um sistema pede o e-mail Atos com o destino confirmado; enquanto ele estiver em preparação, a área do Workspace avisa e o cadastro de sistemas espera.
Preço
Ativar é gratuito: é aceitar os termos do Atos Workspace, sem cartão nem pagamento. O Atos Workspace Pro, opcional, custa R$ 29 por mês (cartão, assinatura) ou R$ 290 por ano (cartão ou Pix).
Desativar
Quando quiser, na área do Workspace. Os aplicativos que você cadastrou continuam funcionando para quem entra neles; para vê-los e mudá-los, você ativa de novo.
Gratuito e Pro
Todo Workspace começa no gratuito. O Atos Workspace Pro custa R$ 29 por mês no cartão (uma assinatura; você cancela quando quiser em Minha conta) ou R$ 290 por ano no cartão ou no Pix (pago de uma vez, sem renovação automática). A cobrança é da Atos, pelo mesmo checkout do PAVLVS; a assinatura e as faturas ficam em Minha conta.
| Gratuito | Pro | |
|---|---|---|
| Sistemas no Login Atos | 2 | 10 |
| Pessoas liberadas por sistema | 100 | 5.000 |
| E-mail Atos | 1 endereço | 1 endereço |
| Formulários | 1, com até 50 envios por dia | 10, com até 1.000 envios por dia (somando todos) |
| Avisos ao sistema | histórico de 30 dias | histórico de 90 dias |
| Registro de entradas | 7 dias | 90 dias, e exportar em CSV |
| Página de status | 1 endereço, a cada 15 minutos | até 10 endereços, a cada minuto, e o aviso de queda por e-mail |
| Página de links | 1 página, até 5 links | 3 páginas, até 50 links cada, e os cliques por dia |
Os limites valem no servidor; ao chegar num, a área do Workspace diz qual. Se o Pro acabar, nada é apagado: o que passa dos limites do gratuito fica fora do plano (sistemas só leitura, que continuam deixando entrar quem já entrava; formulários e páginas de links acima do limite parados ou fora do ar; endereços de status sem conferência) até você voltar ao Pro ou reduzir. A conferência “a cada minuto” depende da fila da Atos e pode atrasar com muitas páginas. Detalhes nos termos.
O Login Atos
O “Entrar com Atos” nos seus sistemas. Por baixo é OpenID Connect, o padrão aberto que as bibliotecas de login já conhecem.
O que o sistema recebe
Um identificador fixo da conta (o sub, que não muda), o nome e o e-mail. Só com o consentimento da pessoa, pedido na primeira vez.
Quem pode entrar
Quem cadastrou o sistema decide: só as pessoas que ele liberar, ou qualquer Conta Atos.
A pessoa no controle
Ela vê os sistemas que têm acesso em Minha conta e pode tirar a permissão quando quiser.
Padrão aberto
Código de autorização com PKCE e um id_token assinado, que o seu sistema confere sozinho pelas chaves públicas.
Como a pessoa vê
O seu sistema manda a pessoa para atos.dev.br. Ela entra (ou cria a conta), confere o que o sistema vai receber e permite. Depois volta ao seu endereço já identificada. Da segunda vez em diante, com a sessão aberta, a volta é direta.
Capturas com dados fictícios.
Passo a passo
-
Crie sua Conta Atos e ative o Atos Workspace
É a mesma conta com que as pessoas vão entrar no seu sistema. Entre (ou crie a conta) e, em atos.dev.br/workspace, aceite os termos do Workspace e ative: é gratuito. Ativar o Atos Workspace.
-
Crie o seu e-mail Atos e entre por ele
Em Atos Workspace › E-mail Atos, escolha o seu endereço
nome@atos.dev.bre confirme o destino (o código que a Atos manda e o e-mail de verificação da Cloudflare). Depois, entre com esse endereço e a senha da sua conta: é a mesma conta. Cadastrar sistemas pede isso, e é esse endereço que quem entra no seu sistema vê como quem o cadastrou; o seu e-mail pessoal não aparece. Se você esquecer a senha, o código vai para o e-mail cadastrado da conta, nunca para o endereço @atos.dev.br. -
Cadastre o aplicativo
Em Atos Workspace › Integrações, cadastre o aplicativo e o acesso:
- O aplicativo: o nome que a pessoa verá, o site, a política de privacidade e os termos.
- O acesso: página ou aplicativo (cliente público, sem segredo) ou servidor (cliente confidencial, com um segredo mostrado uma vez só: guarde-o na hora).
- O endereço de volta: exato, como o seu sistema vai mandar no
redirect_uri. Emhttps, ouhttpsó em127.0.0.1e[::1](para testar na sua máquina; um programa instalado pode aceitar a volta em qualquer porta).
-
Escolha quem pode entrar
Dois modos, e você troca quando quiser:
- Qualquer Conta Atos: para um sistema aberto ao público, como uma loja. Quem ainda não tem conta cria uma no caminho: com um botão “Criar conta” ao lado do “Entrar com Atos” (o pedido leva
prompt=create), a Atos abre direto no cadastro, e a pessoa volta ao seu sistema já logada. Veja a loja aberta na prática. - Só as pessoas liberadas (o padrão): para um sistema fechado, como o painel da equipe. Só os e-mails da Conta Atos que você liberar entram (você, que cadastrou, sempre entra). Quem não está liberado não entra, mesmo que crie a conta: a Atos avisa a pessoa, e a volta ao seu sistema traz
error=access_denied. Tirar alguém da lista vale no próximo login e no/oauth/userinfo; umid_tokenjá emitido vale até vencer. Veja o sistema fechado na prática.
- Qualquer Conta Atos: para um sistema aberto ao público, como uma loja. Quem ainda não tem conta cria uma no caminho: com um botão “Criar conta” ao lado do “Entrar com Atos” (o pedido leva
-
Integre
O bloco “Como integrar”, em Integrações, traz o seu
client_ide os endereços, um comando (PowerShell ou Linux/macOS) que grava no.envdo seu projeto o emissor, oclient_ide a volta (o segredo, você cola; ele nunca vai no comando), o código na sua linguagem e um pedido pronto para colar no Claude Code. Ou siga a referência técnica abaixo com a biblioteca OpenID Connect da sua linguagem. Para o botão, use o oficial: o botão “Entrar com Atos” (SVG, HTML e CSS sem script, e as regras de uso). No Chrome e no Edge, a pessoa pode continuar sem sair do seu site. -
Teste
Entre com uma conta liberada, confira o nome e o e-mail que chegaram e tente com uma que não está liberada. Depois tire o acesso em Minha conta e veja o pedido de permissão aparecer de novo. Um exemplo completo, com as telas, está em Na prática.
Na sua linguagem
O mínimo para entrar com a Atos em cada linguagem, com a biblioteca OpenID Connect consagrada dela: a biblioteca confere a assinatura e os campos do id_token, sem criptografia escrita à mão. Os exemplos de servidor são de um acesso confidencial e leem do .env ATOS_ISSUER, ATOS_CLIENT_ID, ATOS_CLIENT_SECRET e ATOS_REDIRECT_URI; no de navegador, troque SEU_CLIENT_ID e a volta pelos do seu acesso.
JavaScript, Python e C#/.NET foram testados contra a Atos: quem está liberado entra, e quem não está volta com error=access_denied e vê o aviso. PHP, Java, Go e Ruby foram escritos pela documentação de cada biblioteca e ainda não rodaram aqui; estão marcados.
Biblioteca: @atos.dev.br/entrar (o pacote da Atos, sem dependências)
Testado contra a Atos: o pacote no Node e num navegador simulado (state, iss, access_denied e id_token forjados recusados), e o servidor Express abaixo.
npm install @atos.dev.br/entrar # Sem npm (página sem empacotador): baixe o mesmo código, um arquivo só, licença MIT: # https://atos.dev.br/pacotes/entrar/0.5.0/entrar.js # No servidor Node, também: npm install express express-session
// Navegador (cliente público, sem segredo). Com empacotador (Vite, webpack...): o pacote do npm.
// Sem empacotador: baixe o entrar.js (o mesmo código) e importe de "./entrar.js".
import { entrarComAtos } from "@atos.dev.br/entrar";
const atos = entrarComAtos({
clientId: "SEU_CLIENT_ID",
voltaPara: "https://seusistema.com.br/retorno", // exatamente como cadastrada
});
// O botão: o oficial de https://atos.dev.br/marca/ (o HTML de lá, com id="entrar-com-atos").
// O clique gera state, nonce e code_verifier, guarda em sessionStorage e vai à Atos.
document.querySelector("#entrar-com-atos").addEventListener("click", () => atos.entrar());
// Na página de volta: confere state e iss, troca o código e confere o id_token (ES256 pela JWKS).
try {
const pessoa = await atos.retorno(); // null quando a página não é uma volta
if (pessoa) console.log(pessoa.id, pessoa.nome, pessoa.email); // identifique pelo id (o sub)
} catch (e) {
if (e.codigo === "access_denied") alert(e.message); // a conta não tem acesso ou não permitiu
else throw e;
}// Servidor Node com Express (cliente confidencial), com o pacote @atos.dev.br/entrar.
// O segredo fica só em ATOS_CLIENT_SECRET (o pacote lê de lá). Rode com: node --env-file=.env servidor.mjs
import express from "express";
import session from "express-session";
import { entrarComAtos } from "@atos.dev.br/entrar";
const atos = entrarComAtos({
clientId: process.env.ATOS_CLIENT_ID,
voltaPara: process.env.ATOS_REDIRECT_URI,
emissor: process.env.ATOS_ISSUER, // vazio: https://atos.dev.br
});
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { httpOnly: true, sameSite: "lax" } }));
app.get("/", (req, res) => {
const p = req.session.pessoa;
res.type("text").send(p ? "Olá, " + p.nome + " (" + p.email + "). id: " + p.id : "Entre em /entrar");
});
// Na sua página, o link para /entrar é o botão oficial: https://atos.dev.br/marca/ (o HTML e a folha botao.css).
app.get("/entrar", async (req, res) => {
const { url, guardar } = await atos.comecar(); // state, nonce e code_verifier
req.session.atos = guardar;
res.redirect(url);
});
app.get(new URL(atos.voltaPara).pathname, async (req, res) => {
const guardado = req.session.atos;
delete req.session.atos;
try {
const pessoa = await atos.concluir(req.originalUrl, guardado); // confere tudo e troca o código
req.session.pessoa = { id: pessoa.id, nome: pessoa.nome, email: pessoa.email };
res.redirect("/");
} catch (e) {
// access_denied: a conta não tem acesso ou a pessoa não permitiu
res.status(e.codigo === "access_denied" ? 403 : 400).type("text").send(e.message);
}
});
app.listen(Number(new URL(atos.voltaPara).port) || 3000);
Biblioteca: Authlib (com Flask)
Testado contra a Atos: Authlib 1.8.0, Flask 3.1.3, Python 3.14 (quem entra e quem não tem acesso).
pip install authlib flask requests
# Entrar com Atos num servidor Python (Flask) com o Authlib: cliente confidencial, PKCE S256.
# O Authlib le a descoberta, gera state, nonce e code_verifier, troca o codigo (client_secret_basic) e confere o
# id_token (ES256 pela JWKS, iss, aud, exp, nonce).
#
# pip install authlib flask requests
# ATOS_CLIENT_ID=... ATOS_CLIENT_SECRET=... ATOS_REDIRECT_URI=http://127.0.0.1:5000/retorno FLASK_SECRET_KEY=... python app.py
import os
from urllib.parse import urlsplit
from authlib.integrations.flask_client import OAuth, OAuthError
from flask import Flask, redirect, session
from markupsafe import escape
ISSUER = os.environ.get("ATOS_ISSUER", "https://atos.dev.br")
VOLTA = os.environ["ATOS_REDIRECT_URI"]
app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET_KEY"] # assina o cookie da sessao do Flask
oauth = OAuth(app)
oauth.register(
"atos",
server_metadata_url=ISSUER + "/.well-known/openid-configuration",
client_id=os.environ["ATOS_CLIENT_ID"],
client_secret=os.environ["ATOS_CLIENT_SECRET"], # so no servidor, nunca no repositorio
client_kwargs={"scope": "openid email profile", "code_challenge_method": "S256"},
)
@app.route("/")
def inicio():
p = session.get("pessoa")
if not p:
return '<a href="/entrar">Entrar com Atos</a>'
return f"Olá, {escape(p['nome'])} ({escape(p['email'])}). id: {escape(p['id'])}. <a href=\"/sair\">Sair</a>"
@app.route("/entrar")
def entrar():
return oauth.atos.authorize_redirect(VOLTA)
@app.route(urlsplit(VOLTA).path)
def retorno():
try:
token = oauth.atos.authorize_access_token() # confere state, troca o codigo e confere o id_token
except OAuthError as e:
if e.error == "access_denied":
return "Sua Conta Atos não tem acesso a este sistema (ou o acesso não foi permitido). Peça a quem administra.", 403
return "Não deu para entrar: " + escape(e.error), 400
claims = token["userinfo"] # o corpo do id_token, ja conferido
# Opcional: pergunta a Atos (userinfo) se o acesso ainda vale.
dados = oauth.atos.userinfo(token=token)
if dados["sub"] != claims["sub"]:
return "Resposta inconsistente da Atos.", 400
session["pessoa"] = {"id": claims["sub"], "nome": claims.get("name", ""), "email": claims.get("email", "")}
return redirect("/")
@app.route("/sair")
def sair():
session.clear()
return redirect("/")
if __name__ == "__main__":
app.run(host="127.0.0.1", port=urlsplit(VOLTA).port or 5000)
Biblioteca: Microsoft.AspNetCore.Authentication.OpenIdConnect
Testado contra a Atos: .NET 10 e o pacote 10.0.11 (quem entra e quem não tem acesso).
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
// Entrar com Atos num servidor ASP.NET Core com o Microsoft.AspNetCore.Authentication.OpenIdConnect: cliente
// confidencial, PKCE S256. O handler le a descoberta, gera state, nonce e code_verifier, troca o codigo e confere o
// id_token (ES256 pela JWKS, iss, aud, exp, nonce); depois le o userinfo.
//
// dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
// ATOS_CLIENT_ID=... ATOS_CLIENT_SECRET=... ATOS_REDIRECT_URI=https://seusistema.com.br/signin-oidc dotnet run
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
static string Env(string nome) => Environment.GetEnvironmentVariable(nome) ?? throw new InvalidOperationException("Falta a variável " + nome);
var issuer = Environment.GetEnvironmentVariable("ATOS_ISSUER") ?? "https://atos.dev.br";
var volta = new Uri(Env("ATOS_REDIRECT_URI"));
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = "Atos";
})
.AddCookie()
.AddOpenIdConnect("Atos", o =>
{
o.Authority = issuer;
o.ClientId = Env("ATOS_CLIENT_ID");
o.ClientSecret = Env("ATOS_CLIENT_SECRET"); // só no servidor, nunca no repositório
o.ResponseType = OpenIdConnectResponseType.Code;
o.ResponseMode = OpenIdConnectResponseMode.Query; // a Atos volta na consulta (o padrão do handler é form_post)
o.UsePkce = true;
o.CallbackPath = volta.AbsolutePath;
o.Scope.Clear();
o.Scope.Add("openid");
o.Scope.Add("email");
o.Scope.Add("profile");
o.GetClaimsFromUserInfoEndpoint = true;
o.MapInboundClaims = false; // os nomes do OpenID Connect (sub, name, email)
o.TokenValidationParameters.NameClaimType = "name";
o.AccessDeniedPath = "/sem-acesso"; // error=access_denied: a conta não tem acesso ou a pessoa não permitiu
o.RequireHttpsMetadata = issuer.StartsWith("https://"); // http só no teste local
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (HttpContext c) => c.User.Identity?.IsAuthenticated == true
? Results.Text($"Olá, {c.User.FindFirst("name")?.Value} ({c.User.FindFirst("email")?.Value}). id: {c.User.FindFirst("sub")?.Value}")
: Results.Text("Entre em /entrar"));
app.MapGet("/entrar", () => Results.Challenge(new AuthenticationProperties { RedirectUri = "/" }, ["Atos"]));
app.MapGet("/sem-acesso", () => Results.Text("Sua Conta Atos não tem acesso a este sistema (ou o acesso não foi permitido). Peça a quem administra.", statusCode: 403));
app.MapGet("/sair", async (HttpContext c) => { await c.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Results.Redirect("/"); });
app.Run();
Biblioteca: league/oauth2-client e firebase/php-jwt
Não testado aqui: escrito pela documentação das bibliotecas. (A jumbojett/openid-connect-php não confere ES256, a assinatura da Atos.)
composer require league/oauth2-client firebase/php-jwt
<?php
// Entrar com Atos em PHP: league/oauth2-client (PKCE S256) e firebase/php-jwt (o id_token ES256 pela JWKS).
// Este arquivo é a ida e a volta: ATOS_REDIRECT_URI aponta para ele (ex.: https://seusistema.com.br/entrar.php).
// Não testado aqui: escrito pela documentação das duas bibliotecas.
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWK;
use Firebase\JWT\JWT;
use League\OAuth2\Client\Provider\GenericProvider;
session_start();
$issuer = getenv('ATOS_ISSUER') ?: 'https://atos.dev.br';
$clientId = getenv('ATOS_CLIENT_ID');
$d = json_decode(file_get_contents($issuer . '/.well-known/openid-configuration'), true);
if (($d['issuer'] ?? '') !== $issuer) { http_response_code(502); exit('A descoberta não é da Atos.'); }
$atos = new GenericProvider([
'clientId' => $clientId,
'clientSecret' => getenv('ATOS_CLIENT_SECRET'), // só no servidor, nunca no repositório
'redirectUri' => getenv('ATOS_REDIRECT_URI'),
'urlAuthorize' => $d['authorization_endpoint'],
'urlAccessToken' => $d['token_endpoint'],
'urlResourceOwnerDetails' => $d['userinfo_endpoint'],
'scopes' => ['openid', 'email', 'profile'],
'scopeSeparator' => ' ',
'pkceMethod' => GenericProvider::PKCE_METHOD_S256,
]);
if (!isset($_GET['code']) && !isset($_GET['error'])) {
// A ida: state, nonce e code_verifier ficam na sessão.
$nonce = bin2hex(random_bytes(16));
$url = $atos->getAuthorizationUrl(['nonce' => $nonce]);
$_SESSION['atos'] = ['state' => $atos->getState(), 'pkce' => $atos->getPkceCode(), 'nonce' => $nonce];
header('Location: ' . $url);
exit;
}
// A volta.
$ida = $_SESSION['atos'] ?? null;
unset($_SESSION['atos']);
if (!$ida || !hash_equals($ida['state'], $_GET['state'] ?? '')) { http_response_code(400); exit('O state não confere.'); }
if (($_GET['error'] ?? '') === 'access_denied') {
http_response_code(403);
exit('Sua Conta Atos não tem acesso a este sistema (ou o acesso não foi permitido). Peça a quem administra.');
}
$atos->setPkceCode($ida['pkce']);
$token = $atos->getAccessToken('authorization_code', ['code' => $_GET['code']]);
JWT::$leeway = 60;
$jwks = json_decode(file_get_contents($d['jwks_uri']), true);
$c = JWT::decode($token->getValues()['id_token'], JWK::parseKeySet($jwks)); // assinatura pelo kid e exp
if ($c->iss !== $issuer || $c->aud !== $clientId || !hash_equals($ida['nonce'], $c->nonce ?? '')) {
http_response_code(400);
exit('O id_token não confere.');
}
session_regenerate_id(true);
$_SESSION['pessoa'] = ['id' => $c->sub, 'nome' => $c->name ?? '', 'email' => $c->email ?? ''];
header('Location: /');
Biblioteca: Spring Security OAuth2 Client (Spring Boot 3)
Não testado aqui: escrito pela documentação do Spring Security.
<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
// Entrar com Atos em Spring Boot 3 com o Spring Security OAuth2 Client: cliente confidencial, PKCE S256.
// Não testado aqui: escrito pela documentação do Spring Security.
//
// application.yml (a volta é https://seusistema.com.br/login/oauth2/code/atos):
//
// spring.security.oauth2.client:
// provider.atos:
// issuer-uri: ${ATOS_ISSUER:https://atos.dev.br}
// user-name-attribute: sub
// registration.atos:
// client-id: ${ATOS_CLIENT_ID}
// client-secret: ${ATOS_CLIENT_SECRET}
// client-authentication-method: client_secret_basic
// authorization-grant-type: authorization_code
// redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
// scope: openid,email,profile
package br.com.seusistema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@Configuration
class SegurancaAtos {
@Bean
SecurityFilterChain seguranca(HttpSecurity http, ClientRegistrationRepository clientes) throws Exception {
var resolver = new DefaultOAuth2AuthorizationRequestResolver(clientes, "/oauth2/authorization");
resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()); // PKCE S256 também no confidencial
http.authorizeHttpRequests(a -> a.requestMatchers("/sem-acesso").permitAll().anyRequest().authenticated())
.oauth2Login(o -> o.authorizationEndpoint(e -> e.authorizationRequestResolver(resolver))
.failureUrl("/sem-acesso")); // error=access_denied cai aqui
return http.build();
}
// A Atos assina o id_token em ES256 (o padrão do Spring é RS256).
@Bean
JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
var f = new OidcIdTokenDecoderFactory();
f.setJwsAlgorithmResolver(c -> SignatureAlgorithm.ES256);
return f;
}
}
@RestController
class Inicio {
@GetMapping("/")
String inicio(@AuthenticationPrincipal OidcUser p) {
return "Olá, " + p.getFullName() + " (" + p.getEmail() + "). id: " + p.getSubject();
}
@GetMapping("/sem-acesso")
String semAcesso() {
return "Sua Conta Atos não tem acesso a este sistema (ou o acesso não foi permitido). Peça a quem administra.";
}
}
Biblioteca: coreos/go-oidc e golang.org/x/oauth2
Não testado aqui: escrito pela documentação das bibliotecas.
go get github.com/coreos/go-oidc/v3/oidc golang.org/x/oauth2
// Entrar com Atos em Go com github.com/coreos/go-oidc/v3 e golang.org/x/oauth2: cliente confidencial, PKCE S256.
// Não testado aqui: escrito pela documentação das duas bibliotecas.
package main
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"log"
"net/http"
"net/url"
"os"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
func aleatorio() string {
b := make([]byte, 24)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(b)
}
func main() {
issuer := os.Getenv("ATOS_ISSUER")
if issuer == "" {
issuer = "https://atos.dev.br"
}
provider, err := oidc.NewProvider(context.Background(), issuer) // lê a descoberta e confere o issuer
if err != nil {
log.Fatal(err)
}
conf := oauth2.Config{
ClientID: os.Getenv("ATOS_CLIENT_ID"),
ClientSecret: os.Getenv("ATOS_CLIENT_SECRET"), // só no servidor, nunca no repositório
RedirectURL: os.Getenv("ATOS_REDIRECT_URI"),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "email", "profile"},
}
verificador := provider.Verifier(&oidc.Config{ClientID: conf.ClientID}) // ES256 pela JWKS, iss, aud, exp
volta, err := url.Parse(conf.RedirectURL)
if err != nil {
log.Fatal(err)
}
guardar := func(w http.ResponseWriter, nome, valor string) {
http.SetCookie(w, &http.Cookie{Name: nome, Value: valor, Path: "/", MaxAge: 600, HttpOnly: true,
Secure: volta.Scheme == "https", SameSite: http.SameSiteLaxMode})
}
http.HandleFunc("/entrar", func(w http.ResponseWriter, r *http.Request) {
state, nonce, verifier := aleatorio(), aleatorio(), oauth2.GenerateVerifier()
guardar(w, "atos_state", state)
guardar(w, "atos_nonce", nonce)
guardar(w, "atos_verifier", verifier)
http.Redirect(w, r, conf.AuthCodeURL(state, oidc.Nonce(nonce), oauth2.S256ChallengeOption(verifier)), http.StatusFound)
})
http.HandleFunc(volta.Path, func(w http.ResponseWriter, r *http.Request) {
lido := func(nome string) string {
c, err := r.Cookie(nome)
if err != nil {
return ""
}
return c.Value
}
q := r.URL.Query()
if lido("atos_state") == "" || q.Get("state") != lido("atos_state") {
http.Error(w, "O state não confere.", http.StatusBadRequest)
return
}
if q.Get("error") == "access_denied" {
http.Error(w, "Sua Conta Atos não tem acesso a este sistema (ou o acesso não foi permitido). Peça a quem administra.", http.StatusForbidden)
return
}
tok, err := conf.Exchange(r.Context(), q.Get("code"), oauth2.VerifierOption(lido("atos_verifier")))
if err != nil {
http.Error(w, "A Atos recusou a troca do código.", http.StatusBadRequest)
return
}
bruto, _ := tok.Extra("id_token").(string)
idt, err := verificador.Verify(r.Context(), bruto)
if err != nil || idt.Nonce != lido("atos_nonce") {
http.Error(w, "O id_token não confere.", http.StatusBadRequest)
return
}
var c struct{ Name, Email string }
if err := idt.Claims(&c); err != nil {
http.Error(w, "id_token ilegível.", http.StatusBadRequest)
return
}
// Crie aqui a sua sessão, identificando a pessoa por idt.Subject (o sub).
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Olá, %s (%s). id: %s", c.Name, c.Email, idt.Subject)
})
porta := os.Getenv("PORT")
if porta == "" {
porta = "8080"
}
log.Fatal(http.ListenAndServe(":"+porta, nil))
}
Biblioteca: omniauth_openid_connect (Rails)
Não testado aqui: escrito pela documentação da gem.
bundle add omniauth_openid_connect omniauth-rails_csrf_protection
# Entrar com Atos em Rails com o omniauth_openid_connect: cliente confidencial, PKCE S256.
# Não testado aqui: escrito pela documentação da gem. A volta é https://seusistema.com.br/auth/atos/callback.
# config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :openid_connect,
name: :atos,
issuer: ENV.fetch("ATOS_ISSUER", "https://atos.dev.br"),
discovery: true, # lê a descoberta e a JWKS; confere o id_token (ES256, iss, aud, exp, nonce)
scope: [:openid, :email, :profile],
response_type: :code,
pkce: true,
client_auth_method: :basic,
client_options: {
identifier: ENV.fetch("ATOS_CLIENT_ID"),
secret: ENV.fetch("ATOS_CLIENT_SECRET"), # só no servidor, nunca no repositório
redirect_uri: ENV.fetch("ATOS_REDIRECT_URI"),
}
end
OmniAuth.config.on_failure = proc { |env| OmniAuth::FailureEndpoint.new(env).redirect_to_failure }
# config/routes.rb
# get "/auth/atos/callback", to: "sessoes#criar"
# get "/auth/failure", to: "sessoes#falha"
# app/controllers/sessoes_controller.rb
class SessoesController < ApplicationController
def criar
auth = request.env["omniauth.auth"]
reset_session
session[:pessoa] = { id: auth.uid, nome: auth.info.name, email: auth.info.email } # uid é o sub
redirect_to root_path
end
def falha
if params[:message] == "access_denied"
render plain: "Sua Conta Atos não tem acesso a este sistema (ou o acesso não foi permitido). Peça a quem administra.", status: :forbidden
else
render plain: "Não deu para entrar.", status: :bad_request
end
end
end
# O botão (POST, com o token CSRF; gem omniauth-rails_csrf_protection):
# <%= button_to "Entrar com Atos", "/auth/atos", data: { turbo: false } %>
“Continuar como”: entrar sem sair do site
No Chrome e no Edge, a pessoa pode entrar sem ir à Atos: o próprio navegador mostra a Conta Atos que está aberta nele, “Continuar como <nome>”, e ela escolhe. Por baixo é o FedCM (Federated Credential Management), o padrão do W3C para isso. Na primeira vez em cada aplicativo, a Atos abre uma janela pequena com a mesma tela de permissão do login de sempre (com quem cadastrou o aplicativo); das outras vezes, a escolha basta.
Onde funciona
Chrome e Edge (o FedCM, com o modo do botão e o params, do Chrome 132 em diante). Testado no Chrome 154 no Windows. Firefox e Safari não têm FedCM: neles, o mesmo botão leva ao login de sempre, pelo redirecionamento. Se a pessoa fecha a janela do navegador, também.
O que o site recebe
Um código, como na volta do login de sempre, e só depois que a pessoa escolhe a conta. O site o troca no /oauth/token com o PKCE e, no acesso confidencial, com o segredo. Nunca o id_token direto do navegador.
Quem pode entrar
O mesmo do aplicativo: no modo “Só as pessoas liberadas”, quem está fora da lista recebe a recusa no navegador e o site recebe o erro access_denied, sem nada da pessoa. A conta só aparece para quem já entrou na Atos neste navegador, depois da verificação em duas etapas, quando ligada.
Privacidade
Para mostrar a conta, o navegador pergunta à Atos sem dizer qual site está aberto, e a resposta fica com o navegador. A Atos diz ao navegador se há alguém dentro dela (o “Login Status”), sem dizer quem. Veja a política de privacidade.
Passo a passo
-
Confira a origem do seu site
O pedido vem da página do seu site, e a Atos confere a origem dela contra o acesso: vale uma origem cadastrada no acesso ou a de um endereço de volta (
https://loja.com.br/auth/voltaautorizahttps://loja.com.br). Nada mais para cadastrar. -
Use o pacote 0.4.0 ou mais novo
O
continuarComFedCM()é do@atos.dev.br/entrar0.4.0 ou mais novo:npm install @atos.dev.br/entrar. Numa página sem empacotador, o mesmo código num arquivo só, servido pela Atos:https://atos.dev.br/pacotes/entrar/0.5.0/entrar.js. -
Ponha o botão
O botão oficial com o texto “Continuar com Atos”, como link para o login de sempre. No clique, chame o FedCM (ele precisa do clique: o modo do botão).
-
Troque o código
Numa página sem servidor (acesso público), o pacote troca e devolve a pessoa. Com servidor (acesso confidencial), o servidor começa (
comecarFedCM(), que guarda ocode_verifierna sessão), a página entrega o código e o servidor troca com o segredo (concluirFedCM()).
// Acesso público: a página troca o código e confere o id_token.
import { entrarComAtos } from "./entrar.js"; // https://atos.dev.br/pacotes/entrar/0.4.0/entrar.js
const atos = entrarComAtos({ clientId: "SEU_CLIENT_ID", voltaPara: "https://seusistema.com.br/retorno" });
botao.addEventListener("click", async (e) => {
e.preventDefault();
// null: foi pelo login de sempre (sem FedCM no navegador, ou a janela fechada)
const pessoa = await atos.continuarComFedCM();
if (pessoa) console.log(pessoa.id, pessoa.nome, pessoa.email);
});
// Acesso confidencial: o servidor começa e troca (com ATOS_CLIENT_SECRET).
app.post("/fedcm/comecar", async (req, res) => {
const { params, guardar } = await atos.comecarFedCM();
req.session.atosFedcm = guardar;
res.json({ params });
});
app.post("/fedcm/concluir", express.json(), async (req, res) => {
const guardado = req.session.atosFedcm;
delete req.session.atosFedcm;
const pessoa = await atos.concluirFedCM(req.body.codigo, guardado);
req.session.pessoa = { id: pessoa.id, nome: pessoa.nome, email: pessoa.email };
res.json({ ok: true });
});
// Na página, no clique:
const { params } = await (await fetch("/fedcm/comecar", { method: "POST" })).json();
const r = await atos.continuarComFedCM({ params, semFedCM: "/entrar" }); // { codigo } ou null
if (r) await fetch("/fedcm/concluir", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ codigo: r.codigo }) });
Num iframe de outra origem, o FedCM precisa de allow="identity-credentials-get". Com uma CSP que limita connect-src, inclua https://atos.dev.br. A Loja da Maria (exemplos/loja-de-exemplo) tem os dois botões funcionando.
Os endereços
- Well-known
https://atos.dev.br/.well-known/web-identity- Configuração (configURL)
https://atos.dev.br/fedcm/config.json- Contas
/fedcm/contas(só o navegador, comSec-Fetch-Dest: webidentity)- Aplicativo
/fedcm/cliente(a política e os termos que o navegador mostra)- Afirmação
/fedcm/afirmar: o código, ou a janela de permissão da Atos (continue_on), ou o erro- Desconectar
/fedcm/desconectar(IdentityCredential.disconnect: tira a permissão do aplicativo)- params
{ nonce, code_challenge, code_challenge_method: "S256", scope }; o código se troca no/oauth/tokensemredirect_uri
Na prática: do zero ao login
Um exemplo rodando de verdade, do cadastro ao “Olá”. A Maria tem um site, a Loja da Maria, e quer que os clientes dela entrem com a Conta Atos. Quem pode entrar depende do modo que ela escolhe na Atos:
- Qualquer Conta Atos, para a loja aberta a clientes: o Pedro, cliente novo, clica em “Criar conta” na loja, cria a Conta Atos, permite e volta à loja já logado. Veja abaixo.
- Só as pessoas liberadas, para um sistema fechado: só os e-mails da lista entram. A Maria libera o João; o Pedro, que não está na lista, não entra. Veja abaixo.
Feito num ambiente de teste com dados fictícios. A Atos rodou nesta máquina, por isso alguns endereços mostram 127.0.0.1. As contas são de mentira, e a loja é a de exemplos/loja-de-exemplo no repositório. Em atos.dev.br, os passos e as telas são os mesmos.
Loja aberta: qualquer Conta Atos
O caso de uma loja: qualquer pessoa pode virar cliente, e quem ainda não tem Conta Atos cria uma no caminho, sem sair do pedido da loja.
-
A Maria cadastra a loja na Atos
Ela ativa o Atos Workspace e cria o e-mail Atos dela,
maria.doces@atos.dev.br, confirmando o destino. Com ele confirmado, a Atos pede que ela entre pelo endereço novo (a mesma senha), e as Integrações abrem.
Ela tinha entrado com o e-mail pessoal: o botão sai e volta à tela de entrar com o endereço Atos preenchido. No ambiente de teste, a verificação da Cloudflare é simulada. Já entrando por
maria.doces@atos.dev.br, em Atos Workspace › Integrações, ela cria o aplicativo “Loja da Maria” e, em “Quem pode entrar”, escolhe “Qualquer Conta Atos”. Depois cria um acesso confidencial, porque a loja tem servidor, com o endereço de volta da loja. A Atos mostra o segredo do acesso uma vez só, e ela o guarda.
O aplicativo, quem pode entrar e o acesso. O endereço de volta é 127.0.0.1porque é o ambiente de teste; num site publicado, é o endereçohttpsdele. -
Ela abre o “Como integrar”
O cartão traz o
client_id, os endereços e um comando que grava no.envdo projeto o emissor, oclient_ide a volta. O segredo ela cola à mão emATOS_CLIENT_SECRET: ele nunca vai no comando.
No ambiente de teste, os endereços são os da Atos local ( 127.0.0.1:8787). Ematos.dev.br, o emissor éhttps://atos.dev.br. -
Ela instala o pacote
Na pasta da loja, um servidor Node com Express, basta um comando: o pacote
@atos.dev.br/entrarvem do npm, junto com o Express e a sessão.
A saída real do npm installdesta demonstração, com o@atos.dev.br/entrar0.1.0 do registro do npm. O “Criar conta” usa a opçãocriarConta, que é do 0.2.0; nas capturas do Pedro, a loja rodou com o 0.2.0 do repositório. Com o 0.1, a opção é ignorada e o botão abre a tela de entrar, que também tem “Criar conta”. -
Ela escreve os trechos
Criar o cliente, mandar a pessoa para a Atos (para entrar ou para criar a conta) e receber a volta. O resto da loja é dela. Este é o trecho do
servidor.mjsda loja de exemplo:import { entrarComAtos } from "@atos.dev.br/entrar"; // 1. O cliente, com o que o .env tem (o segredo o pacote lê sozinho de ATOS_CLIENT_SECRET). const atos = entrarComAtos({ clientId: process.env.ATOS_CLIENT_ID, voltaPara: process.env.ATOS_REDIRECT_URI, // exatamente como cadastrada na Atos emissor: process.env.ATOS_ISSUER || undefined, // vazio: https://atos.dev.br }); // 2. "Entrar com Atos" vai a /entrar; "Criar conta", a /criar-conta: a Atos abre em "Criar a conta Atos". async function irParaAtos(req, res, extra) { const { url, guardar } = await atos.comecar(extra); req.session.atos = guardar; res.redirect(url); } app.get("/entrar", (req, res) => irParaAtos(req, res)); app.get("/criar-conta", (req, res) => irParaAtos(req, res, { criarConta: true })); // 3. A Atos devolve a pessoa aqui (o endereço de volta cadastrado). O pacote confere tudo e troca o código. app.get(new URL(atos.voltaPara).pathname, async (req, res) => { const guardado = req.session.atos; delete req.session.atos; try { const pessoa = await atos.concluir(req.originalUrl, guardado); req.session.pessoa = { id: pessoa.id, nome: pessoa.nome, email: pessoa.email }; res.redirect("/"); } catch (e) { // access_denied: a conta não está liberada na Loja, ou a pessoa não permitiu. if (e.codigo === "access_denied") return res.redirect("/sem-acesso"); // ... (a página de erro da loja, com e.message) } });O que o pacote faz por trás
comecar()sorteia ostate, ononcee ocode_verifierdo PKCE e monta o endereço da Atos. Com{ criarConta: true }, o endereço leva tambémprompt=create. A loja só guarda o que ele devolve na sessão de quem está entrando.concluir()confere se ostatee o emissor que voltaram são os esperados e troca o código pelo token, com o segredo e ocode_verifier. Depois confere oid_token: a assinatura, pelas chaves públicas da Atos; se foi emitido para esta loja; se está na validade; e se ononceé o do começo. A volta é igual para quem entrou e para quem acabou de criar a conta.- Se a Atos voltou com
error=access_denied, oconcluir()lança um erro comcodigoigual a"access_denied", e a loja decide o que mostrar.
-
O Pedro, cliente novo, abre a loja
A loja mostra “Entrar com Atos”, para quem já tem conta, e “Criar conta”. O Pedro ainda não tem Conta Atos e clica em “Criar conta”. O clique leva ao
/criar-contada loja, que manda o Pedro para a Atos.
O site é da Maria, com a cara dela. Os dois botões levam à Atos; o “Criar conta” pede que ela abra no cadastro. -
Ele cria a Conta Atos
A Atos abre direto em “Criar a conta Atos”, para continuar na Loja da Maria. Ele preenche o nome, o e-mail e a senha, e a Atos manda um código de 6 dígitos para o e-mail dele. Com o código confirmado, a conta existe e a sessão na Atos está aberta. Quem já tem conta clica em “Já tem conta? Entrar” e segue no mesmo pedido.
Na Atos, aberta pelo “Criar conta” da loja. O código chega por e-mail; no ambiente de teste, onde o e-mail não sai, ele foi lido da Atos local. -
Ele permite
Como a loja aceita qualquer Conta Atos, a conta nova entra sem ninguém liberar. É a primeira vez, então a Atos mostra quem cadastrou a loja, avisa que ela não é um aplicativo da Atos e diz o que a loja vai receber. Ele permite.
O consentimento aparece só na primeira vez, com quem cadastrou a loja e o que ela vai receber. -
De volta à loja, já logado
A Atos devolve o Pedro ao endereço de volta com um código. A loja chama o
concluir(), que confere tudo e entrega o identificador, o nome e o e-mail. A loja abre a sessão dela e mostra o “Olá”. Da próxima vez, ele clica em “Entrar com Atos”; com a sessão da Atos ainda aberta, a volta é direta.
O nome e o e-mail vieram da Atos, conferidos pelo pacote. Para guardar o cliente, a loja usa o id, que não muda.
Sistema fechado: só as pessoas liberadas
O caso de um sistema que não é para qualquer um: uma área só para quem já fez encomenda, o painel da equipe. Os passos de cadastro e de código são os mesmos; muda o modo do aplicativo.
-
A Maria escolhe “Só as pessoas liberadas”
Em “Quem pode entrar”, ela escolhe “Só as pessoas liberadas” (o modo padrão) e libera o e-mail do João. Ela, que cadastrou, sempre entra.
A lista de quem pode entrar. Um e-mail liberado que ainda não tem Conta Atos entra quando a pessoa cria a conta com ele. -
O João entra e permite
Na Atos, ele entra com o e-mail e a senha da Conta Atos dele. Como é a primeira vez, a Atos mostra quem cadastrou a loja, avisa que ela não é um aplicativo da Atos e diz o que a loja vai receber. Ele permite.
Na Atos: “Para continuar em Loja da Maria”. A senha fica na Atos; a loja não a recebe.
O consentimento aparece só na primeira vez. -
De volta à loja, já identificado
A volta é a mesma da loja aberta: o
concluir()confere tudo, e a loja mostra o “Olá”.
O João, liberado, dentro da área de clientes. -
E o Pedro, que não está liberado
O Pedro tem Conta Atos, mas a Maria não liberou o e-mail dele. A Atos não pede consentimento nem entrega código: avisa que a conta dele não tem acesso. Quando ele volta à loja, a volta traz
error=access_denied, oconcluir()lança esse erro e a loja mostra a página de “sem acesso” dela. Neste modo, o “Criar conta” não abre a porta: quem cria a Conta Atos sem estar na lista fica com a conta, mas chega a esta mesma tela.
Na Atos: a conta do Pedro não está na lista da Loja da Maria.
Na loja, depois do “Voltar ao aplicativo”: o texto é da Maria, que decide o que dizer a quem não tem acesso.
Para rodar a loja de exemplo contra a Atos de verdade, veja o README de exemplos/loja-de-exemplo: como cadastrar o aplicativo, escolher o modo e gravar o .env.
Avisos ao seu sistema
Em cada aplicativo (Atos Workspace › Integrações), cadastre um endereço https do seu sistema. A Atos mostra um segredo uma vez e manda ali, assinado com ele, um POST JSON {id, tipo, criado, app, dados} quando o acesso das pessoas muda. Assim o seu sistema encerra a sessão de quem tirou a permissão sem esperar o token vencer.
- acesso.revogado
- A pessoa tirou a permissão do seu aplicativo em Minha conta.
dados: {sub}. - liberado.adicionado
- Você liberou e-mails na lista.
dados: {pessoas: [{email, sub?}]}: osubsó de quem já permitiu o aplicativo; de quem nunca entrou, só o e-mail. - liberado.removido
- Você tirou um e-mail da lista. O mesmo formato.
- aviso.teste
- O botão “Mandar um aviso de teste”.
dados: {}. - Assinatura
Atos-Assinatura: t=<segundos>,v1=<HMAC-SHA256 hex de "<t>.<corpo>">, com o segredo do aviso;Atos-Eventotraz oid(o mesmo em toda tentativa: aplique uma vez). Recuse o que tiver mais de 5 minutos.- Entrega
- Responda 2xx. Senão, a Atos tenta de novo em 1 min, 5 min, 30 min, 2 h e 12 h e marca “falhou”; na área você vê cada entrega, a última resposta e “Tentar de novo”. Ela espera até 10 s e não segue redirecionamentos. O endereço precisa ser público (sem IP, rede interna ou a própria Atos).
O registro de entradas
Também em cada aplicativo: quem entrou nos últimos 7 dias (no gratuito) ou 90 dias (no Pro) (o sub, e o e-mail e o nome só com os escopos email e profile permitidos), quando, o lugar aproximado e o navegador resumido, e as recusas de quem não está liberado (sem dizer quem). Com filtro por período, resultado e busca; no Pro, exportar em CSV.
Conferir no Node
import { verificarAviso } from "@atos.dev.br/entrar"; // 0.3.0 ou mais novo
app.post("/atos/avisos", express.text({ type: "application/json" }), async (req, res) => {
let aviso;
try {
aviso = await verificarAviso(req.body, req.get("atos-assinatura"), process.env.ATOS_AVISOS_SEGREDO);
} catch {
return res.sendStatus(401);
}
if (aviso.tipo === "acesso.revogado") await encerrarSessoesDe(aviso.dados.sub);
res.sendStatus(200);
});
Passe o corpo cru (o texto, antes do JSON.parse). Em outra linguagem: calcule o HMAC-SHA256 de t + "." + corpo com o segredo, compare em tempo constante com v1 e confira a idade de t.
Página de status
Em Atos Workspace › Status, cadastre os endereços https do seu sistema (uma página por conta; pede o e-mail Atos confirmado, como os Formulários): no gratuito, 1 endereço conferido a cada 15 minutos; no Pro, até 10 a cada minuto, mais ou menos. A Atos publica, se você quiser, atos.dev.br/status/<nome>: no ar, instável ou fora, o tempo de resposta e o histórico de 30 ou 90 dias, sem as URLs. No Pro, se você ligar, ela avisa por e-mail (no destino do seu e-mail Atos) quando um endereço cai, depois de duas conferências seguidas fora, e quando volta.
- A conferência
GETcomUser-Agent: AtosStatus/1, sem seguir redirecionamentos, até 10 s, sem ler o corpo. 2xx e 3xx: no ar (acima de 3 s, lento); 4xx, 5xx, erro ou tempo esgotado: fora.- Os endereços
- Só https público, na porta padrão: sem IP, rede interna, nomes reservados ou a Atos. Um endereço leve, como
/saude, é o melhor. - Histórico
- 90 dias. A página pública não carrega script e fica 60 s em cache.
Página de links
Em Atos Workspace › Página de links, crie atos.dev.br/@nome: o seu nome, uma frase curta, uma foto opcional e a lista dos seus links, com o seu e-mail Atos e um formulário seu como botões, se quiser. A Atos gera a página (sem código seu e sem script), com o domínio de destino escrito embaixo de cada link. O e-mail Atos não é exigido.
- Os links
- Só https, sem endereço IP, rede interna, encurtador de links (o destino precisa ficar à vista) nem endereço da Atos. Títulos com o nome da Atos são recusados.
- O nome
- De 3 a 30 letras minúsculas, números e hífen. Nomes que lembrem a Atos, bancos, o governo ou marcas conhecidas são recusados, e o nome não pode ser o e-mail Atos de outra pessoa.
- Embaixo, sempre
- “Página criada por uma pessoa com Conta Atos. A Atos não verificou estes links.” e “Denunciar esta página”: a denúncia chega à equipe da Atos, que pode tirar a página do ar (a pessoa vê o motivo).
- Os cliques
- No Pro, cada link passa por
atos.dev.br/@nome/ir/…e a Atos conta os cliques por dia e por link (90 dias), sem guardar nada de quem clicou. No gratuito, os links vão direto e nada é contado. - Limites
- Gratuito: 1 página, até 5 links. Pro: 3 páginas, até 50 links cada. A página fica até 1 minuto em cache.
Formulários
Para o formulário de contato de um site estático, sem servidor seu. Em Atos Workspace › Formulários, você dá um nome, diz de quais sites ele é enviado (a origem, como https://exemplo.com.br; até 5) e escolhe os campos (nome, e-mail, assunto, mensagem e até 5 livres). A Atos dá o endereço https://atos.dev.br/f/<id>; cada envio vira um e-mail da Atos no destino confirmado do seu e-mail Atos, e responder a ele vai para o e-mail que a pessoa digitou.
A Atos não guarda o que foi preenchido: do envio, fica só quando, o site, o resultado e o tamanho, por 30 dias, no registro do formulário. Envios de outros sites são recusados; campos que você não escolheu são ignorados; arquivos não são aceitos.
No HTML
<form action="https://atos.dev.br/f/SEU_FORMULARIO" method="post">
<label>Nome <input name="nome"></label>
<label>E-mail <input name="email" type="email" required></label>
<label>Mensagem <textarea name="mensagem" required></textarea></label>
<!-- A armadilha contra robôs: fica escondida e vazia. -->
<div style="position:absolute;left:-10000px" aria-hidden="true"><input name="_armadilha" tabindex="-1" autocomplete="off"></div>
<button type="submit">Enviar</button>
</form>
Depois de enviar, a pessoa vê a página de “obrigado” da Atos (com o botão de voltar ao site) ou vai para o endereço que você escolher, num dos sites do formulário. A área do Workspace mostra o exemplo já com o endereço e os campos do seu formulário.
Por fetch
const r = await fetch("https://atos.dev.br/f/SEU_FORMULARIO", {
method: "POST",
headers: { "content-type": "application/json", accept: "application/json" },
body: JSON.stringify({ nome, email, mensagem }),
});
const d = await r.json(); // {ok: true} ou {ok: false, erro, codigo}
O CORS só libera os sites cadastrados no formulário.
Contra robôs
- Sempre
- A origem de cada envio tem de ser um dos sites cadastrados; o campo-armadilha
_armadilha(quem o preenche recebe o “obrigado” e nada sai); 5 envios por hora do mesmo endereço de internet em cada formulário; o limite por dia que você escolhe (até 200); 32 KB por envio; e um teto diário da Atos inteira. - Verificação
- Quando a Atos tem o Cloudflare Turnstile ligado, você pode pedi-lo em cada formulário: o envio passa por uma página da Atos com o desafio antes da entrega. Com ele ligado, o envio por fetch/JSON é recusado (
codigo: "verificacao"). Sem ele na Atos, a opção não aparece. - Respostas
- Por fetch:
200 {ok: true};400campos inválidos;403site não cadastrado ou formulário desligado;404formulário não encontrado;413grande demais;429limite;503o formulário não está recebendo (o e-mail Atos do dono parado, o Workspace desativado) ou o teto da Atos;502o e-mail não saiu (tente de novo).
Referência técnica
Para quem integra à mão. Qualquer biblioteca OpenID Connect que lê a descoberta configura o resto sozinha a partir do emissor.
Endereços
- Emissor (issuer)
https://atos.dev.br- Descoberta
https://atos.dev.br/.well-known/openid-configuration- Autorização
https://atos.dev.br/entrar- Token
https://atos.dev.br/oauth/token- Dados da pessoa
https://atos.dev.br/oauth/userinfo- Chaves públicas (JWKS)
https://atos.dev.br/oauth/jwks- Revogação
https://atos.dev.br/oauth/revogar
Como funciona
- Fluxo
- Código de autorização (
response_type=code), com a volta na consulta (response_mode=query). - PKCE
- Obrigatório para todos os clientes, só com
S256. - Escopos
openid(obrigatório),emaileprofile.- state e nonce
- O
statevolta como foi mandado; ononcevem dentro doid_token. A volta também traziss=https://atos.dev.br. - Código
- Vale 2 minutos e uma vez só. Trocado em
POST /oauth/tokencom ocode_verifiere o mesmoredirect_uri. - Cliente confidencial
- Manda o segredo por
client_secret_basicouclient_secret_post. O cliente público não usa segredo. - id_token
- JWT assinado em
ES256. Confira a assinatura pela JWKS (pelokid) e os camposiss,aud(o seuclient_id),expenonce. - Campos
sub; comemail:emaileemail_verified; comprofile:name. Tambémauth_timeeamr.- amr
- Como a pessoa entrou na Atos, nos valores da RFC 8176:
pwd(senha),fed(pelo Google),otp(o código do aplicativo autenticador ou um código de recuperação),hwkouswk(uma chave de acesso presa ao aparelho ou sincronizada na nuvem dele),user(a pessoa confirmou no aparelho) emfa(passou por mais de um fator). Exemplos:["pwd"],["pwd","otp","mfa"],["hwk","user","mfa"]. Para exigir duas etapas no seu sistema, confira seamrtemmfa; sem ele, a entrada não passou por duas etapas (a pessoa pode não ter a verificação ligada na conta Atos) e o seu sistema decide o que fazer, por exemplo pedir que ela ligue em Minha conta › Segurança. Uma sessão aberta na Atos antes de oamrexistir (outubro de 2026; as sessões duram até 30 dias) não o traz: trate a falta como “só a senha”. - Validade
id_tokeneaccess_tokenvalem 1 hora. Não hárefresh_token: para outra hora, peça o login de novo (com a sessão aberta na Atos, a volta é direta).- prompt
none,login,consentecreate; tambémmax_ageelogin_hint.nonenão combina com outro valor (error=invalid_request).- prompt=create
- Abre a Atos em “Criar a conta Atos” (Initiating User Registration via OpenID Connect 1.0; a descoberta anuncia
createemprompt_values_supported). Lá, “Já tem conta? Entrar” continua o mesmo pedido. Com a sessão já aberta na Atos, o pedido segue como sempre: a permissão, ou a volta direta. Só a volta com o código e oid_tokendiz que a pessoa entrou; quem entra depende do modo do aplicativo, e a conta criada fora da lista volta comaccess_denied. No pacote JavaScript:entrar({ criarConta: true })ecomecar({ criarConta: true }). - Erros na volta
error=access_deniedquando a pessoa não permite ou não está liberada; comprompt=none,login_requiredouconsent_required.
O pedido de autorização
https://atos.dev.br/entrar
?client_id=SEU_CLIENT_ID
&redirect_uri=https%3A%2F%2Fseusistema.com.br%2Fretorno
&response_type=code
&scope=openid%20email%20profile
&state=VALOR_ALEATORIO
&nonce=OUTRO_VALOR_ALEATORIO
&code_challenge=BASE64URL_DO_SHA256_DO_VERIFIER
&code_challenge_method=S256
As quebras de linha são só para ler: a URL vai numa linha só.
A troca do código
POST https://atos.dev.br/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=CODIGO_RECEBIDO
&redirect_uri=https%3A%2F%2Fseusistema.com.br%2Fretorno
&client_id=SEU_CLIENT_ID
&code_verifier=O_VERIFIER_DO_INICIO
A resposta traz id_token, access_token (Bearer, para /oauth/userinfo), expires_in e scope.
Veja também os Termos de uso e a Política de privacidade da Conta Atos.