diff --git a/src/backend/src/FoodDiary.API/Authentication/AuthenticationExtensions.cs b/src/backend/src/FoodDiary.API/Authentication/AuthenticationExtensions.cs new file mode 100644 index 000000000..ce91b6595 --- /dev/null +++ b/src/backend/src/FoodDiary.API/Authentication/AuthenticationExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Google; +using Microsoft.Extensions.DependencyInjection; + +namespace FoodDiary.API.Authentication; + +[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")] +public static class AuthenticationExtensions +{ + public static AuthenticationBuilder AddCustomGoogle( + this AuthenticationBuilder builder, + string authenticationScheme, + Action configureOptions) + { + return builder.AddOAuth( + authenticationScheme, + GoogleDefaults.DisplayName, + configureOptions); + } +} \ No newline at end of file diff --git a/src/backend/src/FoodDiary.API/Authentication/CustomGoogleHandler.cs b/src/backend/src/FoodDiary.API/Authentication/CustomGoogleHandler.cs new file mode 100644 index 000000000..fb20f8cbc --- /dev/null +++ b/src/backend/src/FoodDiary.API/Authentication/CustomGoogleHandler.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Google; +using Microsoft.AspNetCore.Authentication.OAuth; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; + +namespace FoodDiary.API.Authentication; + +public class CustomGoogleHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) + : GoogleHandler(options, logger, encoder) +{ + protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) + { + var query = QueryHelpers.ParseQuery(new Uri(base.BuildChallengeUrl(properties, redirectUri)).Query); + + SetQueryParam(query, properties, OAuthChallengeProperties.ScopeKey, base.FormatScope, Options.Scope); + SetQueryParam(query, properties, GoogleChallengeProperties.AccessTypeKey, Options.AccessType); + SetQueryParam(query, properties, GoogleChallengeProperties.ApprovalPromptKey); + SetQueryParam(query, properties, GoogleChallengeProperties.PromptParameterKey, "select_account"); + SetQueryParam(query, properties, GoogleChallengeProperties.LoginHintKey); + SetQueryParam(query, properties, GoogleChallengeProperties.IncludeGrantedScopesKey, v => v?.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(), new bool?()); + + query["state"] = (StringValues) Options.StateDataFormat.Protect(properties); + + return QueryHelpers.AddQueryString(Options.AuthorizationEndpoint, query); + } + + private static void SetQueryParam( + IDictionary queryStrings, + AuthenticationProperties properties, + string name, + Func formatter, + T defaultValue) + { + var parameter = properties.GetParameter(name); + string str; + if (parameter != null) + str = formatter(parameter); + else if (!properties.Items.TryGetValue(name, out str)) + str = formatter(defaultValue); + properties.Items.Remove(name); + if (str == null) + return; + queryStrings[name] = (StringValues) str; + } + + private static void SetQueryParam( + IDictionary queryStrings, + AuthenticationProperties properties, + string name, + string? defaultValue = null) + { + SetQueryParam(queryStrings, properties, name, (Func) (x => x), defaultValue); + } +} \ No newline at end of file diff --git a/src/backend/src/FoodDiary.API/Startup.cs b/src/backend/src/FoodDiary.API/Startup.cs index 87729fe8c..860b7e79f 100644 --- a/src/backend/src/FoodDiary.API/Startup.cs +++ b/src/backend/src/FoodDiary.API/Startup.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Threading.Tasks; +using FoodDiary.API.Authentication; using FoodDiary.API.Extensions; using FoodDiary.API.Middlewares; using FoodDiary.API.Options; @@ -58,7 +59,7 @@ public void ConfigureServices(IServiceCollection services) return Task.CompletedTask; }; }) - .AddGoogle(Constants.AuthenticationSchemes.OAuthGoogle, options => + .AddCustomGoogle(Constants.AuthenticationSchemes.OAuthGoogle, options => { options.SignInScheme = Constants.AuthenticationSchemes.Cookie; options.ClientId = _googleAuthOptions.ClientId;