O framework ABP Zero disponibiliza um módulo de autenticação externa robusto que permite integrar diversos provedores de terceiros. O fluxo básico consiste em validar as credenciais junto ao provedor, recuperar os dados do usuário e processar o registro ou login na base de dados do sistema.
No ecossistema do ABP, a persistência dessas informações ocorre principalmente em duas tabelas: AbpUsers, que armazena os dados cadastrais do perfil, e AbpUserLogins, que mapeia as credenciais externas. Os campos fundamentais para essa integração são:
- LoginProvider: Nome do provedor (ex: WeChatMiniProgram).
- ProviderKey: Identificador exclusivo do usuário no proveodr (OpenID).
- TenantId: Identificador do locatário.
- UserId: Chave estrangeira para o usuário interno.
Implementando o Provider de Autenticação
Para o WeChat Mini Program, é necessário criar uma classe que herde de ExternalAuthProviderApiBase. Esta classe será responsável por trocar o código temporário enviado pelo cliente pelo OpenID do usuário através da API oficial da Tencent.
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Abp.AspNetZeroCore.Web.Authentication.External;
using Newtonsoft.Json.Linq;
using System.Linq;
namespace MyProject.Web.Authentication.External
{
public class WeChatMiniProgramAuthProvider : ExternalAuthProviderApiBase
{
public const string ProviderName = "WeChatMiniProgram";
private readonly IExternalAuthConfiguration _externalAuthConfig;
private const string WeChatApiUrl = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&grant_type=authorization_code&js_code={2}";
public WeChatMiniProgramAuthProvider(IExternalAuthConfiguration externalAuthConfig)
{
_externalAuthConfig = externalAuthConfig;
}
public override async Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
{
// O accessCode aqui é recebido como um JSON contendo o código e o nome do usuário
var payload = JObject.Parse(accessCode);
var jsCode = payload["Code"]?.ToString();
var nickName = payload["NickName"]?.ToString();
if (string.IsNullOrEmpty(jsCode))
{
throw new ArgumentException("Código do WeChat inválido.");
}
var settings = _externalAuthConfig.Providers.First(p => p.Name == ProviderName);
var sessionData = await FetchWeChatSession(settings.ClientId, settings.ClientSecret, jsCode);
return new ExternalAuthUserInfo
{
Provider = ProviderName,
ProviderKey = sessionData.OpenId,
Name = nickName,
Surname = nickName,
EmailAddress = $"{sessionData.OpenId}@wechat.com" // Email fictício para compatibilidade
};
}
private async Task<WeChatSessionResult> FetchWeChatSession(string appId, string secret, string code)
{
using (var httpClient = new HttpClient())
{
var requestUrl = string.Format(WeChatApiUrl, appId, secret, code);
var response = await httpClient.GetStringAsync(requestUrl);
var jsonResult = JObject.Parse(response);
if (jsonResult["errcode"] != null && jsonResult["errcode"].Value<int>() != 0)
{
throw new Exception($"Erro na API WeChat: {jsonResult["errmsg"]}");
}
return new WeChatSessionResult
{
OpenId = jsonResult["openid"].ToString(),
SessionKey = jsonResult["session_key"].ToString()
};
}
}
}
internal class WeChatSessionResult
{
public string OpenId { get; set; }
public string SessionKey { get; set; }
}
}
Configuração do Ambiente
As credenciais (AppId e Secret) devem ser configuradas no arquivo appsettings.json dentro da seção de autenticação externa. Além disso, no módulo WebHost (ou WebMvc), é necessário registrar o provedor caso ele esteja habilitado nas configurações do sistema.
Um ponto crítico na integração com Mini Programs é o tratamento do ProviderKey. Diferente de fluxos OAuth2 padrão, o OpenID não deve transitar abertamente. No entanto, o ABP exige que o ProviderKey retornado pelo método GetUserInfo corresponda ao que será buscado no banco de dados.
Customizando o Fluxo no Controller
No TokenAuthController, é recomendável criar um método específico para o WeChat para evitar efeitos colatearis em outras integrações. O foco aqui é garantir que o login seja realizado utilizando o identificador único recuperado da API da Tencent.
[HttpPost]
public async Task<ExternalAuthenticateResultModel> WeChatLogin([FromBody] ExternalAuthenticateModel model)
{
// Recupera informações validadas do provedor
var externalUser = await GetExternalUserInfo(model);
// Tenta realizar o login com o OpenID (ProviderKey) retornado
var loginResult = await _logInManager.LoginAsync(
new UserLoginInfo(model.AuthProvider, externalUser.ProviderKey, model.AuthProvider),
GetTenancyNameOrNull()
);
if (loginResult.Result == AbpLoginResultType.Success)
{
return await CreateAuthResult(loginResult);
}
if (loginResult.Result == AbpLoginResultType.UnknownExternalLogin)
{
// Se o usuário não existe, registra automaticamente
var newUser = await RegisterExternalUserAsync(externalUser);
// Novo login após registro
var retryLogin = await _logInManager.LoginAsync(
new UserLoginInfo(model.AuthProvider, externalUser.ProviderKey, model.AuthProvider),
GetTenancyNameOrNull()
);
return await CreateAuthResult(retryLogin);
}
throw _abpLoginResultTypeHelper.CreateExceptionForFailedLoginAttempt(loginResult.Result, model.ProviderKey, GetTenancyNameOrNull());
}
private async Task<ExternalAuthenticateResultModel> CreateAuthResult(AbpLoginResult<Tenant, User> loginResult)
{
var token = CreateAccessToken(CreateJwtClaims(loginResult.Identity));
return new ExternalAuthenticateResultModel
{
AccessToken = token,
EncryptedAccessToken = GetEncrpyedAccessToken(token),
ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds
};
}
Ao realizar a chamada para a API WeChatLogin, o cliente deve enviar tanto o AuthProvider quanto o ProviderAccessCode (que no caso deste exemplo é o JSON contendo o código de sessão). Uma vez autenticado, o servidor retornará o JWT para as chmaadas subsequentes, que deve ser enviado no cabeçalho Authorization: Bearer [token].