Реализация внешней аутентификации для мобильного приложения в ASP.NET WebApi 2
Я пытаюсь создать API (используя ASP.NET WebApi), который будет использоваться собственным мобильным приложением для школьного проекта. (Я не беспокоюсь о / разработке мобильного приложения, эта ответственность ложится на другого участника) Я нахожусь в точке, где мне нужно реализовать авторизацию на основе токенов на Facebook. Существует множество учебных пособий по реализации этой функции для приложений на основе браузера (это довольно просто, и большинство из них встроено), но я не думаю, что следую, как это будет работать с нативными приложениями. Что я не понимаю, как перенаправления будут работать?
По этой ссылке, мой сервер не должен ничего обрабатывать. И я не думаю, что понимаю, как это будет работать? Как будут обрабатываться токены из Facebook?
Кроме того, какую часть обработки токенов я должен реализовать, я не смог найти хорошую документацию для аутентификации внешнего входа в WebApi.
В любом случае, если бы кто-то мог указать мне на точный поток обмена токенами и на то, что реализовано по умолчанию в ASP.NET, это было бы очень полезно.
Кроме того, самая большая путаница для меня - я не понимаю, как будет обрабатываться токен, возвращенный Facebook.
- Я предполагаю, что токен будет возвращен клиенту (мобильное приложение), как мне получить доступ к нему на моем сервере?
- Как создать локальный токен из токена 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.
Поток выглядит примерно так:
- Клиент аутентифицируется через Facebook любым способом (мы использовали https://oauth.io/)
- Facebook возвращает им токен
- Клиент отправляет информацию о токене в конечную точку регистрации моего контроллера WebApi
- Токен проверяется с помощью API Graph Facebook, который возвращает информацию о пользователе
- Пользователь создается в базе данных через ASP.NET Identity с его идентификатором пользователя Facebook в качестве ключа
- Клиент отправляет информацию о токене в конечную точку аутентификации моего контроллера WebApi
- Токен проверяется с помощью API Graph Facebook, который возвращает информацию о пользователе
- Информация о пользователе используется для поиска пользователей в базе данных, подтверждения их ранее зарегистрированной
- ASP.NET Identity используется для создания нового токена для этого пользователя.
- Этот токен возвращается клиенту
- Клиент включает заголовок 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}");
}