Реализация внешней аутентификации для мобильного приложения в ASP.NET WebApi 2

Я пытаюсь создать API (используя ASP.NET WebApi), который будет использоваться собственным мобильным приложением для школьного проекта. (Я не беспокоюсь о / разработке мобильного приложения, эта ответственность ложится на другого участника) Я нахожусь в точке, где мне нужно реализовать авторизацию на основе токенов на Facebook. Существует множество учебных пособий по реализации этой функции для приложений на основе браузера (это довольно просто, и большинство из них встроено), но я не думаю, что следую, как это будет работать с нативными приложениями. Что я не понимаю, как перенаправления будут работать?

По этой ссылке, мой сервер не должен ничего обрабатывать. И я не думаю, что понимаю, как это будет работать? Как будут обрабатываться токены из Facebook?

Кроме того, какую часть обработки токенов я должен реализовать, я не смог найти хорошую документацию для аутентификации внешнего входа в WebApi.

В любом случае, если бы кто-то мог указать мне на точный поток обмена токенами и на то, что реализовано по умолчанию в ASP.NET, это было бы очень полезно.

Кроме того, самая большая путаница для меня - я не понимаю, как будет обрабатываться токен, возвращенный Facebook.

  1. Я предполагаю, что токен будет возвращен клиенту (мобильное приложение), как мне получить доступ к нему на моем сервере?
  2. Как создать локальный токен из токена Facebook? Это все сделано внутренне / автоматически с помощью ASP.NET?

Извините, если это то, что я должен был выяснить. Я провел немало исследований и обнаружил, что тону в (связанной и не связанной) информации. Я не думаю, что даже знаю, как искать нужную мне информацию.

Некоторые ссылки, которые я прочитал:

Проверка подлинности на основе утверждений и токенов (веб-API ASP.NET)

Аутентификация на основе токенов с использованием ASP.NET Web API 2, Owin и Identity

Внешние входы в ASP.NET Web API 2 через Facebook и Google в приложении AngularJS

1 ответ

Решение

Мне пришлось сделать почти то же самое для приложения, над которым я работал. У меня также было много проблем с поиском информации об этом. Казалось, что все, что я нашел, было близко к тому, что мне нужно, но не совсем решение. В итоге я взял кусочки из разных постов, статей и т. Д. И собрал их все вместе, чтобы заставить его работать.

Я помню две ссылки, которые вы опубликовали: "Утверждения на основе утверждений и токенов" и "Внешние учетные записи ASP.NET Web API 2 с Facebook и Google в приложении AngularJS" как имеющие полезную информацию.

Я не могу дать вам исчерпывающий ответ, так как я не помню всего, что я должен был сделать, и при этом я даже не понимал всего, что я делал в то время, но я могу дать вам общее представление. Ты на правильном пути.

По сути, я использовал токен, предоставленный Facebook, чтобы подтвердить, что они вошли в свою учетную запись Facebook, создали пользователя на основе их идентификатора пользователя Facebook и предоставили им свой собственный токен на предъявителя, который они могли использовать для доступа к моему API.

Поток выглядит примерно так:

  1. Клиент аутентифицируется через Facebook любым способом (мы использовали https://oauth.io/)
    • Facebook возвращает им токен
  2. Клиент отправляет информацию о токене в конечную точку регистрации моего контроллера WebApi
    • Токен проверяется с помощью API Graph Facebook, который возвращает информацию о пользователе
    • Пользователь создается в базе данных через ASP.NET Identity с его идентификатором пользователя Facebook в качестве ключа
  3. Клиент отправляет информацию о токене в конечную точку аутентификации моего контроллера WebApi
    • Токен проверяется с помощью API Graph Facebook, который возвращает информацию о пользователе
    • Информация о пользователе используется для поиска пользователей в базе данных, подтверждения их ранее зарегистрированной
    • ASP.NET Identity используется для создания нового токена для этого пользователя.
    • Этот токен возвращается клиенту
  4. Клиент включает заголовок Authorization во все будущие HTTP-запросы с новым токеном, предоставленным моей службой (например, "Authorization: Bearer TOKEN")
    • Если конечная точка WebApi имеет атрибут [Authorize], ASP.NET Identity автоматически проверит токен-носитель и откажется в доступе, если он недопустим

В итоге появилось много пользовательского кода для реализации OAuth с помощью ASP.NET Identity, и те ссылки, которые вы включили, показывают вам некоторые из них. Надеюсь, эта информация поможет вам немного, извините, я не мог помочь больше.

Я следил за этой статьей. Поток в основном такой

  • На сервере есть ключи facebook, как и при веб-входе
  • Приложение запрашивает доступные входы в социальные сети и отображает кнопки (я думаю, вы можете жестко это закодировать)
  • При нажатии кнопки приложение открывает браузер и устанавливает URL-адрес, связанный с указанным входом в социальную сеть. Затем ASP.NET перенаправляет браузер на facebook/google/ что угодно с соответствующим вызовом.
  • Пользователь мог войти в систему или нет, и мог дать разрешение вашему приложению или нет. После того, как он предоставит разрешения, facebook перенаправляется обратно на предоставленный URL-адрес обратного вызова
  • На этом этапе вы можете получить внешнюю информацию для входа из SignInManager и проверить, существует ли уже пользователь и следует ли вам создать новую учетную запись.
  • Наконец, генерируется токен, и браузер перенаправляется на URL-адрес, в который помещен токен. Приложение получает токен из URL-адреса и закрывает браузер. Использует токен для обработки запросов API.

Честно говоря, я понятия не имею, законен ли такой подход...

Код кнопок действий должен перенаправлять на:

public async Task<IEnumerable<ExternalLoginDto>> GetExternalLogins(string returnUrl, bool generateState = false)
{
    IEnumerable<AuthenticationScheme> loginProviders = await SignInManager.GetExternalAuthenticationSchemesAsync();
    var logins = new List<ExternalLoginDto>();

    string state;

    if (generateState)
    {
        const int strengthInBits = 256;
        state = RandomOAuthStateGenerator.Generate(strengthInBits);
    }
    else
    {
        state = null;
    }

    foreach (AuthenticationScheme authenticationScheme in loginProviders)
    {
        var routeValues = new
        {
            provider = authenticationScheme.Name,
            response_type = "token",
            client_id = Configuration["Jwt:Issuer"],
            redirect_uri = $"{Request.Scheme}//{Request.Host}{returnUrl}",
            state = state
        };

        var login = new ExternalLoginDto
        {
            Name = authenticationScheme.DisplayName,
            Url = Url.RouteUrl("ExternalLogin", routeValues),
            State = state
        };

        logins.Add(login);
    }

    return logins;
}

Код для обратного вызова:

[Authorize(AuthenticationSchemes = "Identity.External")]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IActionResult> GetExternalLogin(string provider, string state = null, string client_id = null, string error = null)
{
    if (error != null)
    {
        ThrowBadRequest(error);
    }

    if (!User.Identity.IsAuthenticated)
    {
        return new ChallengeResult(provider);
    }

    string providerKey = User.FindFirstValue(ClaimTypes.NameIdentifier);

    var externalLoginInfo = new ExternalLoginInfo(User, User.Identity.AuthenticationType, providerKey, User.Identity.AuthenticationType);

    if (externalLoginInfo.LoginProvider != provider)
    {
        await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
        return new ChallengeResult(provider);
    }

    var userLoginInfo = new UserLoginInfo(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, externalLoginInfo.ProviderDisplayName);
    User user = await UserManager.FindByLoginAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey);

    if (client_id != Configuration["Jwt:Issuer"])
    {
        return Redirect($"/#error=invalid_client_id_{client_id}");
    }

    if (user != null)
    {
        return await LoginWithLocalUser(user, state);
    }
    else
    {
        string email = null;
        string firstName = null;
        string lastName = null;

        IEnumerable<Claim> claims = externalLoginInfo.Principal.Claims;
        if (externalLoginInfo.LoginProvider == "Google")
        {
            email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
            firstName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
            lastName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
        }
        else if (externalLoginInfo.LoginProvider == "Facebook")
        {
            email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;

            string[] nameParts = claims.First(c => c.Type == ClaimTypes.Name)?.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            firstName = nameParts?.First();
            lastName = nameParts?.Last();
        }

        //some fallback just in case
        firstName ??= externalLoginInfo.Principal.Identity.Name;
        lastName ??= externalLoginInfo.Principal.Identity.Name;

        user = new User
        {
            UserName = email,
            Email = email,
            FirstName = firstName,
            LastName = lastName,
            EmailConfirmed = true //if the user logs in with Facebook consider the e-mail confirmed
        };

        IdentityResult userCreationResult = await UserManager.CreateAsync(user);
        if (userCreationResult.Succeeded)
        {
            userCreationResult = await UserManager.AddLoginAsync(user, userLoginInfo);
            if (userCreationResult.Succeeded)
            {
                return await LoginWithLocalUser(user, state);
            }
        }

        string identityErrrors = String.Join(" ", userCreationResult.Errors.Select(ie => ie.Description));
        Logger.LogWarning($"Error registering user with external login. Email:{email}, Errors:" + Environment.NewLine + identityErrrors);
        return Redirect($"/#error={identityErrrors}");
    }
}

private async Task<RedirectResult> LoginWithLocalUser(User user, string state)
{
    await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

    DateTime expirationDate = DateTime.UtcNow.AddDays(365);

    string token = user.GenerateJwtToken(Configuration["Jwt:Key"], Configuration["Jwt:Issuer"], expirationDate);
    return Redirect($"/#access_token={token}&token_type=bearer&expires_in={(int)(expirationDate - DateTime.UtcNow).TotalSeconds}&state={state}");
}
Другие вопросы по тегам