Hoppa till innehåll

Inloggning med Facebook i ASP.NET Core

I det här inlägget beskrivs hur du kan lägga till Facebook-inloggning på din webbplats i ASP.NET Core. Du måste skapa en Facebook Applikation och lägga till Facebook Login som en produkt. Du behöver App ID och App Secret avseende din Facebook App för att kunna använda api:et, du måste också se till att du lägger till din callback url till Valid OAuth Redirect URI i inställningarna för Facebook Login.

När användaren loggar in med sitt Facebook-konto för första gången eller när användaren ansluter sitt konto till Facebook måste du spara användarens Facebook-ID i din databas. Ett Facebook-ID är ett långt heltal (Int64) men kan med fördel sparas som en sträng. Facebook-ID kommer att användas för att hitta användaren när han loggar in för andra gången.

Tjänster

Vi kommer att behöva en HttpClient och en sessionstjänst för att kunna implementera Facebook-inloggning, vi lägger till dessa tjänster i metoden ConfigureServices i klassen StartUp i vårt projekt. Det är inte bra att löpande skapa och kassera HttpClients i ett program, man bör använda statiska HttpClients eller IHttpClientFactory. Vi använder IHttpClientFactory för att skapa en namngiven klient. Jag måste också lägga till tjänster för autentisering och auktorisation, jag har lagt till ett auktorisationsschema för administratörer.

  1. using System;
  2. using System.Globalization;
  3. using System.Threading.Tasks;
  4. using Microsoft.AspNetCore.Builder;
  5. using Microsoft.AspNetCore.Hosting;
  6. using Microsoft.AspNetCore.Http;
  7. using Microsoft.AspNetCore.Rewrite;
  8. using Microsoft.AspNetCore.Authentication;
  9. using Microsoft.Extensions.Configuration;
  10. using Microsoft.Extensions.DependencyInjection;
  11. using Microsoft.Extensions.Hosting;
  12. using Annytab.Middleware;
  13. using Annytab.Repositories;
  14. using Annytab.Options;
  15. using Annytab.Rules;
  16. using System.Net.Http.Headers;
  17. using System.Net.Http;
  18. using System.Net;
  19. namespace Hockeytabeller
  20. {
  21. /// <summary>
  22. /// This class handles application startup
  23. /// </summary>
  24. public class Startup
  25. {
  26. /// <summary>
  27. /// Variables
  28. /// </summary>
  29. public IConfiguration configuration { get; set; }
  30. /// <summary>
  31. /// Create a new startup object
  32. /// </summary>
  33. /// <param name="configuration">A reference to configuration</param>
  34. public Startup(IConfiguration configuration)
  35. {
  36. this.configuration = configuration;
  37. } // End of the constructor method
  38. /// <summary>
  39. /// This method is used to add services to the container.
  40. /// </summary>
  41. /// <param name="services"></param>
  42. public void ConfigureServices(IServiceCollection services)
  43. {
  44. // Add the mvc framework
  45. services.AddRazorPages();
  46. // Add memory cache
  47. services.AddDistributedMemoryCache();
  48. // Add redis distributed cache
  49. if (configuration.GetSection("AppSettings")["RedisConnectionString"] != "")
  50. {
  51. services.AddDistributedRedisCache(options =>
  52. {
  53. options.Configuration = configuration.GetSection("AppSettings")["RedisConnectionString"];
  54. options.InstanceName = "Hockeytabeller:";
  55. });
  56. }
  57. // Add cors
  58. services.AddCors(options =>
  59. {
  60. options.AddPolicy("AnyOrigin", builder => builder.AllowAnyOrigin());
  61. });
  62. // Add the session service
  63. services.AddSession(options =>
  64. {
  65. // Set session options
  66. options.IdleTimeout = TimeSpan.FromMinutes(20d);
  67. options.Cookie.Name = ".Hockeytabeller";
  68. options.Cookie.Path = "/";
  69. options.Cookie.HttpOnly = true;
  70. options.Cookie.SameSite = SameSiteMode.Lax;
  71. options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
  72. });
  73. // Create database options
  74. services.Configure<DatabaseOptions>(options =>
  75. {
  76. options.connection_string = configuration.GetSection("AppSettings")["ConnectionString"];
  77. options.sql_retry_count = 1;
  78. });
  79. // Create cache options
  80. services.Configure<CacheOptions>(options =>
  81. {
  82. options.expiration_in_minutes = 240d;
  83. });
  84. // Add Authentication
  85. services.AddAuthentication()
  86. .AddCookie("Administrator", options =>
  87. {
  88. options.ExpireTimeSpan = TimeSpan.FromDays(1);
  89. options.Cookie.MaxAge = TimeSpan.FromDays(1);
  90. options.Cookie.HttpOnly = true;
  91. options.Cookie.SameSite = SameSiteMode.Lax;
  92. options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
  93. options.Events.OnRedirectToLogin = (context) =>
  94. {
  95. context.Response.StatusCode = StatusCodes.Status401Unauthorized;
  96. context.Response.Redirect("/admin_login");
  97. return Task.CompletedTask;
  98. };
  99. })
  100. .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("ApiAuthentication", null);
  101. // Add clients
  102. services.AddHttpClient("default", client =>
  103. {
  104. client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  105. }).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate });
  106. // Add repositories
  107. services.AddSingleton<IDatabaseRepository, MsSqlRepository>();
  108. services.AddSingleton<IWebsiteSettingRepository, WebsiteSettingRepository>();
  109. services.AddSingleton<IAdministratorRepository, AdministratorRepository>();
  110. services.AddSingleton<IFinalRepository, FinalRepository>();
  111. services.AddSingleton<IGroupRepository, GroupRepository>();
  112. services.AddSingleton<IStaticPageRepository, StaticPageRepository>();
  113. services.AddSingleton<IXslTemplateRepository, XslTemplateRepository>();
  114. services.AddSingleton<ISitemapRepository, SitemapRepository>();
  115. services.AddSingleton<IXslProcessorRepository, XslProcessorRepository>();
  116. services.AddSingleton<ICommonServices, CommonServices>();
  117. } // End of the ConfigureServices method
  118. /// <summary>
  119. /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  120. /// </summary>
  121. public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IWebsiteSettingRepository website_settings_repository)
  122. {
  123. // Use error handling
  124. if (env.IsDevelopment())
  125. {
  126. app.UseDeveloperExceptionPage();
  127. }
  128. else
  129. {
  130. app.UseStatusCodePagesWithReExecute("/home/error/{0}");
  131. }
  132. // Get website settings
  133. KeyStringList settings = website_settings_repository.GetAllFromCache();
  134. bool redirect_https = settings.Get("REDIRECT-HTTPS") == "true" ? true : false;
  135. bool redirect_www = settings.Get("REDIRECT-WWW") == "true" ? true : false;
  136. bool redirect_non_www = settings.Get("REDIRECT-NON-WWW") == "true" ? true : false;
  137. // Add redirection and use a rewriter
  138. RedirectHttpsWwwNonWwwRule rule = new RedirectHttpsWwwNonWwwRule
  139. {
  140. status_code = 301,
  141. redirect_to_https = redirect_https,
  142. redirect_to_www = redirect_www,
  143. redirect_to_non_www = redirect_non_www,
  144. hosts_to_ignore = new string[] { "localhost", "hockeytabeller001.azurewebsites.net" }
  145. };
  146. RewriteOptions options = new RewriteOptions();
  147. options.Rules.Add(rule);
  148. app.UseRewriter(options);
  149. // Use static files
  150. app.UseStaticFiles(new StaticFileOptions
  151. {
  152. OnPrepareResponse = ctx =>
  153. {
  154. // Cache static files for 30 days
  155. ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=25920000");
  156. ctx.Context.Response.Headers.Append("Expires", DateTime.UtcNow.AddDays(300).ToString("R", CultureInfo.InvariantCulture));
  157. }
  158. });
  159. // Use sessions
  160. app.UseSession();
  161. // For most apps, calls to UseAuthentication, UseAuthorization, and UseCors must
  162. // appear between the calls to UseRouting and UseEndpoints to be effective.
  163. app.UseRouting();
  164. // Use CORS
  165. app.UseCors();
  166. // Use authentication and authorization middlewares
  167. app.UseAuthentication();
  168. app.UseAuthorization();
  169. // Routing endpoints
  170. app.UseEndpoints(endpoints =>
  171. {
  172. endpoints.MapControllerRoute(
  173. "default",
  174. "{controller=home}/{action=index}/{id?}");
  175. });
  176. } // End of the Configure method
  177. } // End of the class
  178. } // End of the namespace

Modeller

Svar från anrop till Facebook Api:et kommer som JSON och vi har skapat modeller för att kunna arbeta med svarsdatan på ett bekvämt sätt. Dessa modeller har skapats baserat på rena JSON-svar från Facebook Api:et. Det är enkelt att serialisera en modell till JSON och det är enkelt att deserialisera JSON till en modell i C#.

  1. using System;
  2. namespace Annytab.Models
  3. {
  4. public class FacebookAuthorization
  5. {
  6. #region Variables
  7. public string access_token { get; set; }
  8. public string token_type { get; set; }
  9. #endregion
  10. #region Constructors
  11. public FacebookAuthorization()
  12. {
  13. // Set values for instance variables
  14. this.access_token = null;
  15. this.token_type = null;
  16. } // End of the constructor
  17. #endregion
  18. } // End of the class
  19. public class FacebookErrorRoot
  20. {
  21. #region Variables
  22. public FacebookError error { get; set; }
  23. #endregion
  24. #region Constructors
  25. public FacebookErrorRoot()
  26. {
  27. // Set values for instance variables
  28. this.error = null;
  29. } // End of the constructor
  30. #endregion
  31. } // End of the class
  32. public class FacebookError
  33. {
  34. #region Variables
  35. public string message { get; set; }
  36. public string type { get; set; }
  37. public Int32? code { get; set; }
  38. public Int32? error_subcode { get; set; }
  39. public string fbtrace_id { get; set; }
  40. #endregion
  41. #region Constructors
  42. public FacebookError()
  43. {
  44. this.message = null;
  45. this.type = null;
  46. this.code = null;
  47. this.error_subcode = null;
  48. this.fbtrace_id = null;
  49. } // End of the constructor
  50. #endregion
  51. } // End of the class
  52. public class FacebookUser
  53. {
  54. #region Variables
  55. public string id { get; set; }
  56. public string name { get; set; }
  57. #endregion
  58. #region Constructors
  59. public FacebookUser()
  60. {
  61. // Set values for instance variables
  62. this.id = null;
  63. this.name = null;
  64. } // End of the constructor
  65. #endregion
  66. } // End of the class
  67. } // End of the namespace

Klass för användare

Jag har skapat en klass som innehåller metoder avseende administratörer och jag har skapat följande metoder för att hämta en Facebook-användare. Denna klass injicerar IHttpClientFactory som client_factory, du måste modifiera FACEBOOK_APP_ID och FACEBOOK_APP_SECRET.

  1. /// <summary>
  2. /// Get a facebook user
  3. /// </summary>
  4. public async Task<FacebookUser> GetFacebookUser(string code)
  5. {
  6. // Create variables
  7. FacebookAuthorization facebook_authorization = null;
  8. FacebookUser facebook_user = new FacebookUser();
  9. // Get a http client
  10. HttpClient client = this.client_factory.CreateClient("default");
  11. // Create the url
  12. string url = "https://graph.facebook.com/oauth/access_token?client_id=" + "FACEBOOK_APP_ID" +
  13. "&redirect_uri=" + "http://localhost:59448" + "/facebook/login_callback" + "&client_secret="
  14. + "FACEBOOK_APP_SECRET" + "&code=" + code;
  15. // Get the response
  16. HttpResponseMessage response = await client.GetAsync(url);
  17. // Make sure that the response is successful
  18. if (response.IsSuccessStatusCode)
  19. {
  20. // Get facebook authorization
  21. facebook_authorization = JsonSerializer.Deserialize<FacebookAuthorization>(await
  22. response.Content.ReadAsStringAsync());
  23. }
  24. else
  25. {
  26. // Get an error
  27. FacebookErrorRoot root = JsonSerializer.Deserialize<FacebookErrorRoot>(await
  28. response.Content.ReadAsStringAsync());
  29. }
  30. // Make sure that facebook authorization not is null
  31. if (facebook_authorization == null)
  32. {
  33. return null;
  34. }
  35. // Modify the url
  36. url = "https://graph.facebook.com/me?fields=id,name&access_token=" + facebook_authorization.access_token;
  37. // Get the response
  38. response = await client.GetAsync(url);
  39. // Make sure that the response is successful
  40. if (response.IsSuccessStatusCode)
  41. {
  42. // Get a facebook user
  43. facebook_user = JsonSerializer.Deserialize<FacebookUser>(await response.Content.ReadAsStringAsync());
  44. }
  45. else
  46. {
  47. // Get an error
  48. FacebookErrorRoot root = JsonSerializer.Deserialize<FacebookErrorRoot>(await response.Content.ReadAsStringAsync());
  49. }
  50. // Return the facebook user
  51. return facebook_user;
  52. } // End of the GetFacebookUser method
  53. /// <summary>
  54. /// Get one administrator based on facebook user id
  55. /// </summary>
  56. public Administrator GetByFacebookUserId(string facebook_user_id)
  57. {
  58. // Create the sql statement
  59. string sql = "SELECT * FROM dbo.administrators WHERE facebook_user_id = @facebook_user_id;";
  60. // Create parameters
  61. IDictionary<string, object> parameters = new Dictionary<string, object>();
  62. parameters.Add("@facebook_user_id", facebook_user_id);
  63. // Get the post
  64. Administrator post = this.database_repository.GetModel<Administrator>(sql, parameters);
  65. // Return the post
  66. return post;
  67. } // End of the GetByFacebookUserId method

Controller

Vår facebook-controller innehåller två metoder för hantering av Facebook-inloggning. Jag måste använda ett AuthenticationScheme för att kunna få användarkontextdata, metoderna är markerade med AllowAnonymous för att tillåta åtkomst från icke autentiserade användare.

  1. using System.Threading.Tasks;
  2. using System.Security.Claims;
  3. using Microsoft.AspNetCore.Mvc;
  4. using Microsoft.AspNetCore.Http;
  5. using Microsoft.AspNetCore.DataProtection;
  6. using Microsoft.AspNetCore.Authentication;
  7. using Microsoft.AspNetCore.Authorization;
  8. using Annytab.Repositories;
  9. using Annytab.Models;
  10. using Annytab.Helpers;
  11. namespace Hockeytabeller.Controllers
  12. {
  13. /// <summary>
  14. /// This class handles facebook login
  15. /// </summary>
  16. [Authorize(AuthenticationSchemes = "Administrator")]
  17. public class facebookController : Controller
  18. {
  19. #region Variables
  20. private readonly IDataProtector data_protector;
  21. private readonly IAdministratorRepository administrator_repository;
  22. #endregion
  23. #region Constructors
  24. /// <summary>
  25. /// Create a new controller
  26. /// </summary>
  27. public facebookController(IDataProtectionProvider provider, IAdministratorRepository administrator_repository)
  28. {
  29. // Set values for instance variables
  30. this.data_protector = provider.CreateProtector("AuthTicket");
  31. this.administrator_repository = administrator_repository;
  32. } // End of the constructor
  33. #endregion
  34. #region Post methods
  35. // Redirect the user to the facebook login
  36. // GET: /facebook/login
  37. [HttpGet]
  38. [AllowAnonymous]
  39. public IActionResult login()
  40. {
  41. // Create a random state
  42. string state = DataValidation.GeneratePassword();
  43. // Add session variables
  44. ControllerContext.HttpContext.Session.Set<string>("FacebookState", state);
  45. ControllerContext.HttpContext.Session.Set<string>("FacebookReturnUrl", "/admin_default");
  46. // Create the url
  47. string url = "https://www.facebook.com/dialog/oauth?client_id=" + "FACEBOOK_APP_ID" + "&state=" +
  48. state + "&response_type=code&redirect_uri=" + "http://localhost:59448" + "/facebook/login_callback";
  49. // Redirect the user
  50. return Redirect(url);
  51. } // End of the login method
  52. // Login the user with facebook
  53. // GET: /facebook/login_callback
  54. [HttpGet]
  55. [AllowAnonymous]
  56. public async Task<IActionResult> login_callback()
  57. {
  58. // Get the state
  59. string state = "";
  60. if (ControllerContext.HttpContext.Request.Query.ContainsKey("state") == true)
  61. {
  62. state = ControllerContext.HttpContext.Request.Query["state"].ToString();
  63. }
  64. // Get sessions
  65. string session_state = ControllerContext.HttpContext.Session.Get<string>("FacebookState");
  66. string return_url = ControllerContext.HttpContext.Session.Get<string>("FacebookReturnUrl");
  67. // Get the code
  68. string code = "";
  69. if (ControllerContext.HttpContext.Request.Query.ContainsKey("code") == true)
  70. {
  71. code = ControllerContext.HttpContext.Request.Query["code"];
  72. }
  73. // Make sure that the callback is valid
  74. if (state != session_state || code == "")
  75. {
  76. return Redirect("/");
  77. }
  78. // Get a facebook user
  79. FacebookUser facebook_user = await this.administrator_repository.GetFacebookUser(code);
  80. // Get the signed in user
  81. Administrator user = this.administrator_repository.GetOneByUsername(HttpContext.User.Identity.Name);
  82. // Check if the user exists or not
  83. if (facebook_user != null && user != null)
  84. {
  85. // Update the user
  86. user.facebook_user_id = facebook_user.id;
  87. this.administrator_repository.Update(user);
  88. // Redirect the user to the return url
  89. return Redirect(return_url);
  90. }
  91. else if (facebook_user != null && user == null)
  92. {
  93. // Check if we can find a user with the facebook id
  94. user = this.administrator_repository.GetByFacebookUserId(facebook_user.id);
  95. // Check if the user exists
  96. if (user == null)
  97. {
  98. // Create a new user
  99. user = new Administrator();
  100. user.admin_user_name = facebook_user.id + "@" + DataValidation.GeneratePassword();
  101. user.facebook_user_id = facebook_user.id;
  102. user.admin_password = PasswordHash.CreateHash(DataValidation.GeneratePassword());
  103. user.admin_role = "Editor";
  104. // Add a user
  105. int id = this.administrator_repository.Add(user);
  106. this.administrator_repository.UpdatePassword(id, user.admin_password);
  107. }
  108. // Create claims
  109. ClaimsIdentity identity = new ClaimsIdentity("Administrator");
  110. //identity.AddClaim(new Claim("administrator", JsonSerializer.Serialize(user)));
  111. identity.AddClaim(new Claim(ClaimTypes.Name, user.admin_user_name));
  112. identity.AddClaim(new Claim(ClaimTypes.Role, user.admin_role));
  113. ClaimsPrincipal principal = new ClaimsPrincipal(identity);
  114. // Sign in the administrator
  115. await HttpContext.SignInAsync("Administrator", principal);
  116. // Redirect the user to the return url
  117. return Redirect(return_url);
  118. }
  119. else
  120. {
  121. // Redirect the user to the start page
  122. return Redirect("/");
  123. }
  124. } // End of the login_callback method
  125. // Sign out the user
  126. // GET: /facebook/logout
  127. [HttpGet]
  128. public async Task<IActionResult> logout()
  129. {
  130. // Sign out the administrator
  131. await HttpContext.SignOutAsync("Administrator");
  132. // Redirect the user to the login page
  133. return RedirectToAction("index", "admin_login");
  134. } // End of the logout method
  135. #endregion
  136. } // End of the class
  137. } // End of the namespace

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *