Inscrição de inquilino e integraçãoTenant sign-up and onboarding

Código de exemplo do GitHubGitHub Sample code

Este artigo descreve como implementar um Inscreva-se processos numa aplicação multi-inquilino, que permite que um cliente para se inscrever a sua organização para a sua aplicação.This article describes how to implement a sign-up process in a multitenant application, which allows a customer to sign up their organization for your application.

Há vários motivos para implementar um processo de inscrição:There are several reasons to implement a sign-up process:

  • Permitir que um administrador de AD dar consentimento para toda a organização do cliente utilizar a aplicação.Allow an AD admin to consent for the customer's entire organization to use the application.
  • Recolha o pagamento de cartão de crédito ou outras informações de clientes.Collect credit card payment or other customer information.
  • Efetue qualquer configuração de uso individual por inquilino necessária para a sua aplicação.Perform any one-time per-tenant setup needed by your application.

Para autenticar com o Azure AD, uma aplicação precisa de aceder ao diretório do utilizador.In order to authenticate with Azure AD, an application needs access to the user's directory. No mínimo, a aplicação tem permissão para ler o perfil do usuário.At a minimum, the application needs permission to read the user's profile. Na primeira vez que um utilizador inicia sessão, o Azure AD mostra uma página de consentimento que lista as permissões que está a ser solicitadas.The first time that a user signs in, Azure AD shows a consent page that lists the permissions being requested. Ao clicar em Accept, o utilizador concede permissão à aplicação.By clicking Accept, the user grants permission to the application.

Por predefinição, o consentimento é concedido numa base por utilizador.By default, consent is granted on a per-user basis. Cada utilizador que inicia sessão vê a página de consentimento.Every user who signs in sees the consent page. No entanto, o Azure AD também suporta consentimento de administrador, que permite que um administrador do AD dar consentimento para uma organização inteira.However, Azure AD also supports admin consent, which allows an AD administrator to consent for an entire organization.

Quando é utilizado o fluxo de consentimento de administrador, a página de consentimento Estados de que o administrador do AD está a conceder permissão em nome de todo o inquilino:When the admin consent flow is used, the consent page states that the AD admin is granting permission on behalf of the entire tenant:

Solicitação de consentimento de administrador

Depois do administrador clicar Accept, outros usuários no mesmo inquilino, podem iniciar sessão e do Azure AD irá ignorar o ecrã de consentimento.After the admin clicks Accept, other users within the same tenant can sign in, and Azure AD will skip the consent screen.

Apenas um administrador do AD pode dar consentimento de administrador, uma vez que ele concede permissão em nome de toda a organização.Only an AD administrator can give admin consent, because it grants permission on behalf of the entire organization. Se tentar autenticar com o fluxo de consentimento do admin não-administrador, o Azure AD apresenta um erro:If a non-administrator tries to authenticate with the admin consent flow, Azure AD displays an error:

Erro de consentimento

Se o aplicativo requer permissões adicionais num momento posterior, o cliente terá de inscrever-se novamente e o consentimento às permissões atualizadas.If the application requires additional permissions at a later point, the customer will need to sign up again and consent to the updated permissions.

Implementação de inscrição de inquilinoImplementing tenant sign-up

Para o [Tailspin Surveys] Tailspin aplicação, definimos vários requisitos para o processo de inscrição:For the Tailspin Surveys application, we defined several requirements for the sign-up process:

  • Um inquilino deve inscrever-se antes dos utilizadores podem iniciar sessão.A tenant must sign up before users can sign in.
  • Inscreva-se utiliza o fluxo de consentimento de administrador.Sign-up uses the admin consent flow.
  • Inscreva-se adiciona o inquilino do utilizador para a base de dados do aplicativo.Sign-up adds the user's tenant to the application database.
  • Depois de um inquilino se inscreve, o aplicativo mostra uma página de integração.After a tenant signs up, the application shows an onboarding page.

Nesta secção, vamos explicar nossa implementação do processo de inscrição.In this section, we'll walk through our implementation of the sign-up process. É importante compreender que "de inscrição" versus "início de sessão" é um conceito de aplicativo.It's important to understand that "sign up" versus "sign in" is an application concept. Durante o fluxo de autenticação do Azure AD não inerentemente sabe se o utilizador está em processo de se inscrever.During the authentication flow, Azure AD does not inherently know whether the user is in process of signing up. Cabe-lhe para a aplicação para controlar o contexto.It's up to the application to keep track of the context.

Quando um utilizador anónimo visita a aplicação de inquéritos, o utilizador é mostrados dois botões, uma para iniciar sessão e um "inscrever a sua empresa" (inscrever-se).When an anonymous user visits the Surveys application, the user is shown two buttons, one to sign in, and one to "enroll your company" (sign up).

Página de inscrição de aplicações

Esses botões invocar ações no AccountController classe.These buttons invoke actions in the AccountController class.

O SignIn ação devolve uma ChallengeResult, que faz com que o middleware OpenID Connect redirecionar para o ponto final de autenticação.The SignIn action returns a ChallengeResult, which causes the OpenID Connect middleware to redirect to the authentication endpoint. Esta é a forma de padrão de autenticação de Acionador no ASP.NET Core.This is the default way to trigger authentication in ASP.NET Core.

[AllowAnonymous]
public IActionResult SignIn()
{
    return new ChallengeResult(
        OpenIdConnectDefaults.AuthenticationScheme,
        new AuthenticationProperties
        {
            IsPersistent = true,
            RedirectUri = Url.Action("SignInCallback", "Account")
        });
}

Agora compare o SignUp ação:Now compare the SignUp action:

[AllowAnonymous]
public IActionResult SignUp()
{
    var state = new Dictionary<string, string> { { "signup", "true" }};
    return new ChallengeResult(
        OpenIdConnectDefaults.AuthenticationScheme,
        new AuthenticationProperties(state)
        {
            RedirectUri = Url.Action(nameof(SignUpCallback), "Account")
        });
}

Como SignIn, o SignUp ação também devolve um ChallengeResult.Like SignIn, the SignUp action also returns a ChallengeResult. Mas, desta vez, adicionamos uma parte da informação de estado para o AuthenticationProperties no ChallengeResult:But this time, we add a piece of state information to the AuthenticationProperties in the ChallengeResult:

  • inscrição: Um sinalizador booleano, que indica que o utilizador foi iniciado o processo de inscrição.signup: A Boolean flag, indicating that the user has started the sign-up process.

As informações de estado na AuthenticationProperties é adicionado para o OpenID Connect estado parâmetro, que de ida e volta durante o fluxo de autenticação.The state information in AuthenticationProperties gets added to the OpenID Connect state parameter, which round trips during the authentication flow.

Parâmetro de estado

Depois do utilizador é autenticado no Azure AD e é redirecionado para a aplicação, o tíquete de autenticação contém o estado.After the user authenticates in Azure AD and gets redirected back to the application, the authentication ticket contains the state. Estamos a utilizar esse fato para se certificar de que o valor de "signup" presentes no fluxo de autenticação completa.We are using this fact to make sure the "signup" value persists across the entire authentication flow.

No Azure AD, o fluxo de consentimento de administrador é acionado, adicionando um parâmetro de "linha de comandos" na cadeia de caracteres de consulta no pedido de autenticação:In Azure AD, the admin consent flow is triggered by adding a "prompt" parameter to the query string in the authentication request:

/authorize?prompt=admin_consent&...

A aplicação de inquéritos adiciona o pedido durante o RedirectToAuthenticationEndpoint eventos.The Surveys application adds the prompt during the RedirectToAuthenticationEndpoint event. Este evento é chamado imediatamente antes do middleware redireciona para o ponto final de autenticação.This event is called right before the middleware redirects to the authentication endpoint.

public override Task RedirectToAuthenticationEndpoint(RedirectContext context)
{
    if (context.IsSigningUp())
    {
        context.ProtocolMessage.Prompt = "admin_consent";
    }

    _logger.RedirectToIdentityProvider();
    return Task.FromResult(0);
}

Definição ProtocolMessage.Prompt informa o middleware para adicionar o parâmetro de "linha de comandos" para o pedido de autenticação.Setting ProtocolMessage.Prompt tells the middleware to add the "prompt" parameter to the authentication request.

Tenha em atenção que a linha de comandos só é necessário durante a inscrição.Note that the prompt is only needed during sign-up. O início de sessão normal deve não incluí-lo.Regular sign-in should not include it. Para distinguir entre eles, verificamos o signup valor no estado de autenticação.To distinguish between them, we check for the signup value in the authentication state. Esta condição verifica o seguinte método de extensão:The following extension method checks for this condition:

internal static bool IsSigningUp(this BaseControlContext context)
{
    Guard.ArgumentNotNull(context, nameof(context));

    string signupValue;
    // Check the HTTP context and convert to string
    if ((context.Ticket == null) ||
        (!context.Ticket.Properties.Items.TryGetValue("signup", out signupValue)))
    {
        return false;
    }

    // We have found the value, so see if it's valid
    bool isSigningUp;
    if (!bool.TryParse(signupValue, out isSigningUp))
    {
        // The value for signup is not a valid boolean, throw

        throw new InvalidOperationException($"'{signupValue}' is an invalid boolean value");
    }

    return isSigningUp;
}

Registar um inquilinoRegistering a tenant

A aplicação de inquéritos armazena algumas informações sobre cada inquilino e utilizador da base de dados do aplicativo.The Surveys application stores some information about each tenant and user in the application database.

Tabela de inquilino

Na tabela de inquilino, IssuerValue é o valor da afirmação do emissor do inquilino.In the Tenant table, IssuerValue is the value of the issuer claim for the tenant. Para o Azure AD, este é https://sts.windows.net/<tentantID> e oferece um valor exclusivo por inquilino.For Azure AD, this is https://sts.windows.net/<tentantID> and gives a unique value per tenant.

Quando um novo inquilino se inscreve, a aplicação de inquéritos escreve um registo de inquilino para a base de dados.When a new tenant signs up, the Surveys application writes a tenant record to the database. Isso acontece dentro do AuthenticationValidated eventos.This happens inside the AuthenticationValidated event. (Não fazê-lo antes deste evento, porque o token de ID não ser validado, ainda, para que não podem confiar que os valores de afirmação.(Don't do it before this event, because the ID token won't be validated yet, so you can't trust the claim values. Ver autenticação.See Authentication.

Eis o código relevante da aplicação de inquéritos:Here is the relevant code from the Surveys application:

public override async Task TokenValidated(TokenValidatedContext context)
{
    var principal = context.AuthenticationTicket.Principal;
    var userId = principal.GetObjectIdentifierValue();
    var tenantManager = context.HttpContext.RequestServices.GetService<TenantManager>();
    var userManager = context.HttpContext.RequestServices.GetService<UserManager>();
    var issuerValue = principal.GetIssuerValue();
    _logger.AuthenticationValidated(userId, issuerValue);

    // Normalize the claims first.
    NormalizeClaims(principal);
    var tenant = await tenantManager.FindByIssuerValueAsync(issuerValue)
        .ConfigureAwait(false);

    if (context.IsSigningUp())
    {
        if (tenant == null)
        {
            tenant = await SignUpTenantAsync(context, tenantManager)
                .ConfigureAwait(false);
        }

        // In this case, we need to go ahead and set up the user signing us up.
        await CreateOrUpdateUserAsync(context.Ticket, userManager, tenant)
            .ConfigureAwait(false);
    }
    else
    {
        if (tenant == null)
        {
            _logger.UnregisteredUserSignInAttempted(userId, issuerValue);
            throw new SecurityTokenValidationException($"Tenant {issuerValue} is not registered");
        }

        await CreateOrUpdateUserAsync(context.Ticket, userManager, tenant)
            .ConfigureAwait(false);
    }
}

Esse código faz o seguinte:This code does the following:

  1. Verifique se o valor de emissor do inquilino já está na base de dados.Check if the tenant's issuer value is already in the database. Se o inquilino não tem sessão iniciada, FindByIssuerValueAsync devolve um valor nulo.If the tenant has not signed up, FindByIssuerValueAsync returns null.
  2. Se o utilizador é inscrever-se:If the user is signing up:
    1. Adicionar o inquilino na base de dados (SignUpTenantAsync).Add the tenant to the database (SignUpTenantAsync).
    2. Adicionar o utilizador autenticado à base de dados (CreateOrUpdateUserAsync).Add the authenticated user to the database (CreateOrUpdateUserAsync).
  3. Caso contrário, conclua o fluxo de início de sessão normal:Otherwise complete the normal sign-in flow:
    1. Se o emissor do inquilino não foi encontrada na base de dados, isso significa que o inquilino não está registado, e o cliente precisa para se inscrever.If the tenant's issuer was not found in the database, it means the tenant is not registered, and the customer needs to sign up. Nesse caso, lança uma exceção para fazer com que a autenticação falha.In that case, throw an exception to cause the authentication to fail.
    2. Caso contrário, crie um registo de base de dados para este utilizador, se existir não um já (CreateOrUpdateUserAsync).Otherwise, create a database record for this user, if there isn't one already (CreateOrUpdateUserAsync).

Aqui está o SignUpTenantAsync método que adiciona ao inquilino à base de dados.Here is the SignUpTenantAsync method that adds the tenant to the database.

private async Task<Tenant> SignUpTenantAsync(BaseControlContext context, TenantManager tenantManager)
{
    Guard.ArgumentNotNull(context, nameof(context));
    Guard.ArgumentNotNull(tenantManager, nameof(tenantManager));

    var principal = context.Ticket.Principal;
    var issuerValue = principal.GetIssuerValue();
    var tenant = new Tenant
    {
        IssuerValue = issuerValue,
        Created = DateTimeOffset.UtcNow
    };

    try
    {
        await tenantManager.CreateAsync(tenant)
            .ConfigureAwait(false);
    }
    catch(Exception ex)
    {
        _logger.SignUpTenantFailed(principal.GetObjectIdentifierValue(), issuerValue, ex);
        throw;
    }

    return tenant;
}

Aqui está um resumo de todo o fluxo Inscreva-se na aplicação de inquéritos:Here is a summary of the entire sign-up flow in the Surveys application:

  1. A utilizador clica no Inscreva-se botão.The user clicks the Sign Up button.
  2. O AccountController.SignUp ação devolve um resultado de desafio.The AccountController.SignUp action returns a challenge result. O estado de autenticação inclui o valor de "signup".The authentication state includes "signup" value.
  3. Na RedirectToAuthenticationEndpoint evento, adicione o admin_consent prompt.In the RedirectToAuthenticationEndpoint event, add the admin_consent prompt.
  4. O middleware de OpenID Connect redireciona para o Azure AD e o utilizador é autenticado.The OpenID Connect middleware redirects to Azure AD and the user authenticates.
  5. Na AuthenticationValidated evento, procure o estado de "signup".In the AuthenticationValidated event, look for the "signup" state.
  6. Adicione o inquilino na base de dados.Add the tenant to the database.

SeguinteNext