Ao criar uma nova solução interna (usando ASP.NET Core MVC), nossa política é autenticar usando nosso Active Directory (também conhecido como Entra) para nossos usuários. Este novo aplicativo então coleta dados que então geram um objeto DocuSign usando sua API/SDKs. Esta é a primeira vez que preciso chamar APIs via SDK, e estou um pouco confuso com a mecânica. Quero seguir as melhores práticas de segurança. O DocuSign recomenda que quando o usuário estiver presente use o Confidential Authorization Code Grant, e fornece exemplos. No entanto, não acho que os exemplos que modificam Program.cs (exemplos mostram usando Startup.cs de versões mais antigas do .NET) funcionem quando combinados com a autenticação Entra.
Resumindo, esta solução deve funcionar desta maneira:
- Todos os usuários são autenticados via Entra ID (Azure AD). (Veja o destaque amarelo)
- Após a autenticação, os usuários podem executar ações que podem exigir a chamada de APIs do DocuSign usando o Fluxo de Código de Autorização OAuth 2.0.
Minhas perguntas (após a atualização, ainda gostaria de entender as 2 primeiras perguntas) :
Acredito (mas não tenho 100% de certeza) que o Entra não usa autenticação de cookies. E não devemos mudar isso.
É possível ter múltiplas Autenticações em um único aplicativo como descrevi usando
Program.cs
AddAuthentication()
? Qual é o propósito deEnableTokenAcquisitionToCallDownstreamApi()
, é usar as credenciais do usuário para obter outros dados que essas credenciais permitem?(Respondido com atualização - Encontrei outra maneira de fazer isso funcionar) Acredito que eu deveria tentar
AddAuthentication()
em Program.cs, em vez de criar solicitações HttpClient, escrever código em controladores para obter o token de acesso e, então, de alguma forma, persistir para chamar as outras APIs do DocuSign. Pensei que encontraria exemplos disso, mas vejo principalmente exemplos usando tokens JWT, para chamadas de API para API. Verdade? Resposta: Sim
Atualização: Por fim, removi o segundo AddAuthentication() do Program.cs:
// Add Entra authentication
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
//pulled out all the other AddAuth, AddOauth...
Eu uso meus controladores para chamar o Docusign SDK. Há um documento que, na parte inferior, tem a classe e os métodos que funcionaram: https://developers.docusign.com/platform/auth/confidential-authcode-get-token/
Usei 3 métodos DocuSignClient.cs na minha camada de controlador. (Veja o código da classe aqui https://github.com/docusign/docusign-esign-csharp-client/blob/master/sdk/src/DocuSign.eSign/Client/DocuSignClient.cs )
GetAuthorizationUri(), GenerateAccessToken(),GetUserInfo()
Funcionou. Talvez a DocuSign forneça exemplos e amostras usando estes, eu não encontrei nenhum, mas foi bem direto. Não estava no código QuickStart deles.
Original: Quando tento o código de exemplo (trechos abaixo), eu autentico com sucesso com o Entra, e quando navego para o controlador com autenticação DocuSign, sou solicitado a fornecer minhas credenciais DocuSign. Tudo bem. Com um ponto de interrupção no OnCreatingTicket
, eu passo por essa parte da autenticação. No entanto, depois disso, recebo uma exceção abaixo. Todas as pesquisas sobre isso dizem que preciso habilitar cookies no meu esquema de autenticação padrão. O que eu não quero fazer.
Exceção:
The authentication handler registered for scheme 'OpenIdConnect' is 'OpenIdConnectHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: Cookies.
Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler<TOptions>.HandleRequestAsync()
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
var builder = WebApplication.CreateBuilder(args);
// Add Entra authentication
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
// Add DocuSign authentication
builder.Services.AddAuthentication(options =>
{
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "DocuSign";
})
.AddCookie()
.AddOAuth("DocuSign", options =>
{
options.ClientId = this.Configuration["DocuSign:ClientId"];
options.ClientSecret = this.Configuration["DocuSign:ClientSecret"];
options.CallbackPath = new PathString("/ds/callback");
options.AuthorizationEndpoint = this.Configuration["DocuSign:AuthorizationEndpoint"];
options.TokenEndpoint = this.Configuration["DocuSign:TokenEndpoint"];
options.UserInformationEndpoint = this.Configuration["DocuSign:UserInformationEndpoint"];
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
user.Add("access_token", context.AccessToken);
user.Add("refresh_token", context.RefreshToken);
user.Add("expires_in", DateTime.Now.Add(context.ExpiresIn.Value).ToString());
using (JsonDocument payload = JsonDocument.Parse(user.ToString()))
{
context.RunClaimActions(payload.RootElement);
}
}
//lines 151 - 244 from https://github.com/docusign/code-examples-csharp/blob/master/launcher-csharp/Startup.cs
// Add controllers and Razor pages with Auth
builder.Services.AddControllersWithViews(options =>
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticateUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
—-----------------
[Authorize(AuthenticationSchemes = "DocuSign")]
public IActionResult Login(string authType = "CodeGrant", string returnUrl = "/")
{
if (authType == "CodeGrant")
{
return Challenge(new AuthenticationProperties() { RedirectUri = returnUrl });
}
}
Aqui está a documentação do DocuSign que usei como orientação: https://developers.docusign.com/docs/esign-rest-api/sdks/csharp/auth/
Se seus usuários estiverem sempre presentes quando estiverem usando sua "solução interna" (seu "aplicativo"), o melhor é configurar sua conta Docusign para usar seu sistema Entra/AD como autenticação SSO. Isso usa o fluxo do Código de Autorização OAuth por meio de um navegador.
Os cookies são usados para criar/manter a sessão de login do Docusign.
Pode funcionar sem cookies, já que seu aplicativo estará armazenando/usando o Access Token. Experimente.
Se seu aplicativo às vezes/sempre for usado sem que o usuário esteja presente, você pode usar o fluxo OAuth JWT. Nesse caso, seu aplicativo verifica (via Entra/AD) se o usuário que inicia a operação é autenticado ou não (e quem ele é). Então seu aplicativo usa essas informações para personificar o usuário obtendo um Access Token associado ao usuário por meio do fluxo JWT OAuth. (Um navegador não é usado para JWT OAuth.)
Alguns desenvolvedores usam o padrão de representação/JWT mesmo se o usuário estiver sempre presente. Fazer isso adiciona complexidade extra de manutenção/configuração, pois seus administradores precisarão manter uma tabela de mapeamento entre os usuários autorizados do Entra/AD e os usuários correspondentes definidos no Docusign.
Adicionado
A Docusign mudou para usar a representação JWT do OAuth há cerca de 8 anos. Desde então, você tem duas opções com a representação JWT. Para representar alguém, agora você precisa do ID de usuário Docusign (que pode ser pesquisado e armazenado em cache). Antes do OAuth, você podia "agir" como eles apenas fornecendo o e-mail deles.
Use uma "conta de usuário de serviço"
Por exemplo, crie um usuário Docusign chamado "RH". E seu aplicativo sempre obtém um token de acesso para representar o usuário de RH. Isso significa que todos os envelopes enviados pelo seu aplicativo serão enviados pelo usuário de RH, pertencerão a esse usuário, etc.
A desvantagem é que o Usuário 1 provavelmente conseguirá ver os envelopes enviados pelo Usuário 2, já que ambos os envelopes, no nível do Docusign, foram enviados pelo mesmo usuário (RH).
Não dê permissões de administrador à conta de usuário do serviço! Se seu aplicativo for apenas enviar envelopes, não há necessidade de o usuário do serviço ser um administrador.
Representar contas de usuários individuais
Essa costuma ser a melhor estratégia, pois permite que os envelopes de cada usuário enviados pelo aplicativo sejam enviados da conta Docusign do usuário. Se o sistema de autorização (AD) fornecer o endereço de e-mail do usuário, o aplicativo pode:
Procure o Docusign UserID do usuário no e-mail. (Faça isso como um usuário administrador. Use o JWT para criar um token de acesso de administrador. Em seguida, procure o usuário para obter o User ID.)
Representar o usuário por meio da autenticação JWT.
O mapeamento de e-mail para ID de usuário pode ser armazenado em cache pelo aplicativo, pois ele não muda. (Isso pressupõe que todos os usuários tenham sua própria conta de usuário Docusign.)