ASP.NET Identity の Facebook 認証フローでは、Facebook OAuth ダイアログによってアクセス トークンではなくコードが追加されるredirect_url
ため、サーバーはこのコードをアクセス トークンと交換できます。例:
http://localhost:49164/signin-facebook?code=…&state=…
私の問題は、クライアントがFacebook SDKを使用するモバイルアプリであり、すぐにアクセストークンが提供されることです。FacebookはSDKを使用するといつもアクセス トークンが提供されるのですが、これを ASP.NET Web API に直接渡すことはできますか?
これはあまり安全ではないことは理解していますが、そもそも可能でしょうか?
ベストアンサー1
解決策を見つけたかどうかはわかりませんが、私も同じようなことをやろうとしていて、まだパズルのピースを組み立てているところです。実際の解決策は提供していないので、回答ではなくコメントとして投稿しようとしましたが、長すぎます。
どうやら、WebAPI Owin OAuthオプションはすべてブラウザベースです。つまり、ネイティブモバイルアプリには適さないブラウザリダイレクトリクエストを大量に必要とします(私のケースでは必要です)。私はまだ調査と実験中ですが、Hongye Sunがブログ投稿のコメントで簡単に説明しているFacebook でログインするには、Facebook SDK を使用して受信したアクセス トークンを、エンドポイントへのグラフ呼び出しを行うことで API 経由で直接検証できます/me
。
グラフ呼び出しによって返された情報を使用することで、ユーザーがすでに登録されているかどうかを確認できます。最後に、Owin のAuthentication.SignIn
方法などを使用してユーザーをサインインさせ、後続のすべての API 呼び出しで使用されるベアラー トークンを返す必要があります。
編集:実は、私は間違っていました。ベアラー トークンは/Token
エンドポイントの呼び出し時に発行され、入力時に次のようなものを受け入れます。
grant_type=password&username=Alice&password=password123
ここでの問題は、パスワードがないことです。これが OAuth メカニズムの要点です。では、他にどのようにして/Token
エンドポイントを呼び出すことができるのでしょうか?
アップデート:ようやく実用的な解決策を見つけましたが、それを機能させるために既存のクラスに追加する必要があったのは次の通りです。
Startup.Auth.cs
public partial class Startup
{
/// <summary>
/// This part has been added to have an API endpoint to authenticate users that accept a Facebook access token
/// </summary>
static Startup()
{
PublicClientId = "self";
//UserManagerFactory = () => new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
UserManagerFactory = () =>
{
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
userManager.UserValidator = new UserValidator<ApplicationUser>(userManager) { AllowOnlyAlphanumericUserNames = false };
return userManager;
};
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
OAuthBearerOptions.AccessTokenProvider = OAuthOptions.AccessTokenProvider;
OAuthBearerOptions.AuthenticationMode = OAuthOptions.AuthenticationMode;
OAuthBearerOptions.AuthenticationType = OAuthOptions.AuthenticationType;
OAuthBearerOptions.Description = OAuthOptions.Description;
OAuthBearerOptions.Provider = new CustomBearerAuthenticationProvider();
OAuthBearerOptions.SystemClock = OAuthOptions.SystemClock;
}
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<ApplicationUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
[Initial boilerplate code]
OAuthBearerAuthenticationExtensions.UseOAuthBearerAuthentication(app, OAuthBearerOptions);
[More boilerplate code]
}
}
public class CustomBearerAuthenticationProvider : OAuthBearerAuthenticationProvider
{
public override Task ValidateIdentity(OAuthValidateIdentityContext context)
{
var claims = context.Ticket.Identity.Claims;
if (claims.Count() == 0 || claims.Any(claim => claim.Issuer != "Facebook" && claim.Issuer != "LOCAL_AUTHORITY" ))
context.Rejected();
return Task.FromResult<object>(null);
}
}
そしてAccountController
、 では、次のアクションを追加しました。
[HttpPost]
[AllowAnonymous]
[Route("FacebookLogin")]
public async Task<IHttpActionResult> FacebookLogin(string token)
{
[Code to validate input...]
var tokenExpirationTimeSpan = TimeSpan.FromDays(14);
ApplicationUser user = null;
// Get the fb access token and make a graph call to the /me endpoint
// Check if the user is already registered
// If yes retrieve the user
// If not, register it
// Finally sign-in the user: this is the key part of the code that creates the bearer token and authenticate the user
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.Id, null, "Facebook"));
// This claim is used to correctly populate user id
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, null, "LOCAL_AUTHORITY"));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new Microsoft.Owin.Infrastructure.SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(tokenExpirationTimeSpan);
var accesstoken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
Authentication.SignIn(identity);
// Create the response
JObject blob = new JObject(
new JProperty("userName", user.UserName),
new JProperty("access_token", accesstoken),
new JProperty("token_type", "bearer"),
new JProperty("expires_in", tokenExpirationTimeSpan.TotalSeconds.ToString()),
new JProperty(".issued", ticket.Properties.IssuedUtc.ToString()),
new JProperty(".expires", ticket.Properties.ExpiresUtc.ToString())
);
var json = Newtonsoft.Json.JsonConvert.SerializeObject(blob);
// Return OK
return Ok(blob);
}
以上です。従来の/Token
エンドポイント応答との唯一の違いは、ベアラー トークンがわずかに短く、有効期限と発行日が GMT ではなく UTC であることです (少なくとも私のマシンでは)。
これが役に立つことを願っています!