diff --git a/Core.Cerberos.Adapters/Common/Constants/Policies.cs b/Core.Cerberos.Adapters/Common/Constants/Policies.cs
new file mode 100644
index 0000000..aa7c248
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/Policies.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+namespace Core.Thalos.BuildingBlocks.Common.Constants
+{
+ ///
+ /// Constants for policy.
+ ///
+ public class Policies
+ {
+ ///
+ /// Defines the access policy for reading mobile-related data.
+ /// This policy grants read-only permissions for retrieving mobile device information,
+ /// user mobile settings, or related data as per the application's authorization scope.
+ ///
+ public const string Read = "Read";
+
+ ///
+ /// Defines the access policy for writing mobile-related data.
+ /// This policy grants permissions to modify, update, or store mobile device information,
+ /// user mobile settings, or related data as per the application's authorization scope.
+ ///
+ public const string Write = "Write";
+ }
+}
diff --git a/Core.Cerberos.Adapters/Common/Constants/Roles.cs b/Core.Cerberos.Adapters/Common/Constants/Roles.cs
new file mode 100644
index 0000000..023b651
--- /dev/null
+++ b/Core.Cerberos.Adapters/Common/Constants/Roles.cs
@@ -0,0 +1,15 @@
+namespace Core.Thalos.BuildingBlocks.Common.Constants
+{
+ public class Roles
+ {
+ ///
+ /// The role for Guest.
+ ///
+ public const string Guest = "684909c4826cd093b4f61c11";
+
+ ///
+ /// The role for Admin.
+ ///
+ public const string Admin = "68407642ec46a0e6fe1e8ec9";
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Authorization/Google/GoogleAuthorization.cs b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Google/GoogleAuthorization.cs
new file mode 100644
index 0000000..199a870
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Google/GoogleAuthorization.cs
@@ -0,0 +1,38 @@
+using Core.Thalos.BuildingBlocks.Authentication.Helpers;
+using Google.Apis.Auth.OAuth2;
+using Google.Apis.Auth.OAuth2.Flows;
+using Microsoft.Extensions.Configuration;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Authorization.Google
+{
+ public class GoogleAuthorization(
+ IGoogleAuthHelper googleHelper, IConfiguration config) : IGoogleAuthorization
+ {
+ private string RedirectUrl = config["Authentication:Google:RedirectUri"]!;
+
+ public async Task ExchangeCodeForToken(string code)
+ {
+ var flow = new GoogleAuthorizationCodeFlow(
+ new GoogleAuthorizationCodeFlow.Initializer
+ {
+ ClientSecrets = googleHelper.GetClientSecrets(),
+ Scopes = googleHelper.GetScopes()
+ });
+
+ var token = await flow.ExchangeCodeForTokenAsync(
+ "user", code, RedirectUrl, CancellationToken.None);
+
+ return new UserCredential(flow, "user", token);
+ }
+
+ public string GetAuthorizationUrl() =>
+ new GoogleAuthorizationCodeFlow(
+ new GoogleAuthorizationCodeFlow.Initializer
+ {
+
+ ClientSecrets = googleHelper.GetClientSecrets(),
+ Scopes = googleHelper.GetScopes(),
+ Prompt = "consent"
+ }).CreateAuthorizationCodeRequest(RedirectUrl).Build().ToString();
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Authorization/Google/IGoogleAuthorization.cs b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Google/IGoogleAuthorization.cs
new file mode 100644
index 0000000..9827aab
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Google/IGoogleAuthorization.cs
@@ -0,0 +1,10 @@
+using Google.Apis.Auth.OAuth2;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Authorization.Google
+{
+ public interface IGoogleAuthorization
+ {
+ string GetAuthorizationUrl();
+ Task ExchangeCodeForToken(string code);
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Authorization/Jwt/PermissionAuthorizationAdapter.cs b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Jwt/PermissionAuthorizationAdapter.cs
new file mode 100644
index 0000000..0bbb5d0
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Jwt/PermissionAuthorizationAdapter.cs
@@ -0,0 +1,9 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Authorization.Jwt
+{
+ public class PermissionAuthorizationAdapter(string[] permission) : IAuthorizationRequirement
+ {
+ public string[] Permission { get; } = permission ?? throw new ArgumentNullException(nameof(permission));
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Authorization/Jwt/PermissionsAuthorizationHandler.cs b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Jwt/PermissionsAuthorizationHandler.cs
new file mode 100644
index 0000000..eaca04c
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Authorization/Jwt/PermissionsAuthorizationHandler.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Authorization.Jwt
+{
+ public class PermissionsAuthorizationHandler : AuthorizationHandler
+ {
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationAdapter requirement)
+ {
+ PermissionAuthorizationAdapter requirement2 = requirement;
+ if (context.User.Claims.Any((x) => x.Type == "roleId" && requirement2.Permission.Contains(x.Value)))
+ {
+ context.Succeed(requirement2);
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Extensions/AuthenticationExtension.cs b/Core.Thalos.BuildingBlocks/Authentication/Extensions/AuthenticationExtension.cs
new file mode 100644
index 0000000..f2b1daf
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Extensions/AuthenticationExtension.cs
@@ -0,0 +1,92 @@
+using Core.Thalos.Adapters.Common.Constants;
+using Core.Thalos.Adapters.Options;
+using Core.Thalos.BuildingBlocks.Authentication.Authorization.Google;
+using Core.Thalos.BuildingBlocks.Authentication.Authorization.Jwt;
+using Core.Thalos.BuildingBlocks.Authentication.Handlers;
+using Core.Thalos.BuildingBlocks.Authentication.Helpers;
+using Core.Thalos.BuildingBlocks.Common.Constants;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+using System.Text;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Extensions
+{
+ ///
+ /// Extension methods for configuring authentication with various Google and JWT setups.
+ ///
+ public static class AuthenticationExtension
+ {
+ ///
+ /// Configures authentication using Google Auth for an API that requires downstream API access.
+ ///
+ /// The to add the services to.
+ /// The containing Google Auth configuration settings.
+ public static void ConfigureAuthentication(this IServiceCollection services, IConfiguration configuration)
+ {
+ var secretKey = configuration.GetSection("SecretKey").Value ?? string.Empty;
+ var _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secretKey));
+ var jwtAppSettingsOptions = configuration.GetSection(nameof(JwtIssuerOptions));
+ var jwtIssuerOptions = jwtAppSettingsOptions.Get();
+
+ var googleClientId = configuration["Authentication:Google:ClientId"];
+ var googleClientSecret = configuration["Authentication:Google:ClientSecret"];
+ var redirectUri = configuration["Authentication:Google:RedirectUri"];
+
+ services.AddAuthentication(options =>
+ {
+ options.DefaultAuthenticateScheme = Schemes.GoogleScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddScheme(Schemes.GoogleScheme, null)
+ .AddGoogle(options =>
+ {
+ options.ClientId = googleClientId!;
+ options.ClientSecret = googleClientSecret!;
+ //options.SaveTokens = true;
+ options.CallbackPath = $"/{redirectUri}";
+ })
+ .AddJwtBearer(Schemes.DefaultScheme, x =>
+ {
+ x.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidIssuer = jwtIssuerOptions?.Issuer,
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidAudience = jwtIssuerOptions?.Audience,
+ IssuerSigningKey = new SymmetricSecurityKey(
+ Encoding.UTF8.GetBytes(configuration["SecretKey"] ?? string.Empty))
+ };
+ });
+
+ services.Configure(options =>
+ {
+ options.Issuer = jwtAppSettingsOptions[nameof(jwtIssuerOptions.Issuer)] ?? string.Empty;
+ options.Audience = jwtAppSettingsOptions[nameof(jwtIssuerOptions.Audience)] ?? string.Empty;
+ options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
+ });
+
+ services.AddSingleton(jwtAppSettingsOptions);
+
+ string[] roles = { Roles.Guest, Roles.Admin };
+
+ services.AddAuthorization(options =>
+ {
+ options.AddPolicy(Policies.Read, policy => policy.Requirements.Add(new PermissionAuthorizationAdapter(roles)));
+ options.AddPolicy(Policies.Write, policy => policy.Requirements.Add(new PermissionAuthorizationAdapter(roles)));
+ });
+
+ services.AddTransient();
+
+ services.AddAuthorization();
+ services.AddScoped();
+ services.AddScoped();
+ }
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Extensions/SwaggerExtension.cs b/Core.Thalos.BuildingBlocks/Authentication/Extensions/SwaggerExtension.cs
new file mode 100644
index 0000000..e6b9379
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Extensions/SwaggerExtension.cs
@@ -0,0 +1,84 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.OpenApi.Any;
+using Microsoft.OpenApi.Interfaces;
+using Microsoft.OpenApi.Models;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Extensions
+{
+ public static class SwaggerExtension
+ {
+ public static void AddSwaggerGen(this IServiceCollection services, IConfiguration configuration)
+ {
+ services.AddSwaggerGen(opts =>
+ {
+ const string schemeName = "oauth2";
+
+ opts.AddSecurityDefinition(schemeName, new OpenApiSecurityScheme
+ {
+ Type = SecuritySchemeType.OAuth2,
+ Scheme = "bearer",
+ BearerFormat = "JWT",
+ Name = "Authorization",
+ In = ParameterLocation.Header,
+
+ Extensions = new Dictionary
+ {
+ ["x-tokenName"] = new OpenApiString("id_token")
+ },
+
+ Flows = new OpenApiOAuthFlows
+ {
+ AuthorizationCode = new OpenApiOAuthFlow
+ {
+ AuthorizationUrl = new Uri("https://accounts.google.com/o/oauth2/v2/auth"),
+ TokenUrl = new Uri("https://oauth2.googleapis.com/token"),
+ Scopes = new Dictionary
+ {
+ { "openid", "OpenID Connect" },
+ { "email", "Access email" },
+ { "profile", "Access profile" }
+ }
+ }
+ }
+ });
+
+ // every operation requires the scheme
+ opts.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ [new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ { Type = ReferenceType.SecurityScheme, Id = schemeName }
+ }
+ ] = new[] { "openid", "email", "profile" }
+ });
+
+ opts.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
+ {
+ Description = "JWT Authorization header using the Bearer scheme",
+ Name = "Authorization",
+ In = ParameterLocation.Header,
+ Type = SecuritySchemeType.Http,
+ Scheme = "bearer",
+ BearerFormat = "JWT"
+ });
+
+ opts.AddSecurityRequirement(new OpenApiSecurityRequirement
+ {
+ {
+ new OpenApiSecurityScheme
+ {
+ Reference = new OpenApiReference
+ {
+ Type = ReferenceType.SecurityScheme,
+ Id = "Bearer"
+ }
+ },
+ Array.Empty()
+ }
+ });
+ });
+ }
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Handlers/GoogleAccessTokenAuthenticationHandler.cs b/Core.Thalos.BuildingBlocks/Authentication/Handlers/GoogleAccessTokenAuthenticationHandler.cs
new file mode 100644
index 0000000..83361a2
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Handlers/GoogleAccessTokenAuthenticationHandler.cs
@@ -0,0 +1,63 @@
+using Core.Thalos.Adapters.Common.Constants;
+using Google.Apis.Auth;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using System.Security.Claims;
+using System.Text.Encodings.Web;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Handlers
+{
+ public class GoogleAccessTokenAuthenticationHandler(IOptionsMonitor options,
+ ILoggerFactory logger,
+ UrlEncoder encoder,
+ IConfiguration config) : AuthenticationHandler(options, logger, encoder)
+ {
+ protected override async Task HandleAuthenticateAsync()
+ {
+ if (!Request.Headers.ContainsKey("Authorization"))
+ return AuthenticateResult.Fail("Missing Authorization header");
+
+ var authHeader = Request.Headers.Authorization.ToString();
+ if (!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
+ return AuthenticateResult.Fail("Invalid Authorization header");
+
+ var idToken = authHeader["Bearer ".Length..].Trim();
+
+ GoogleJsonWebSignature.Payload payload;
+ try
+ {
+ payload = await GoogleJsonWebSignature.ValidateAsync(
+ idToken,
+ new GoogleJsonWebSignature.ValidationSettings
+ {
+ Audience = new[] { config["Authentication:Google:ClientId"]! }
+ });
+ }
+ catch (InvalidJwtException)
+ {
+ return AuthenticateResult.Fail("Invalid Google token");
+ }
+
+ var claims = new List
+ {
+ new Claim(ClaimTypes.NameIdentifier, payload.Subject),
+ new Claim(ClaimTypes.Email, payload.Email),
+ new Claim(ClaimTypes.Name, payload.Name ?? "")
+ };
+
+ var identity = new ClaimsIdentity(claims, Schemes.GoogleScheme);
+ var principal = new ClaimsPrincipal(identity);
+
+ var userEmail = principal.FindFirst(ClaimTypes.Email)?.Value;
+
+ if (string.IsNullOrEmpty(userEmail) ||
+ !userEmail.EndsWith("@agilewebs.com", StringComparison.OrdinalIgnoreCase))
+ return AuthenticateResult.Fail("Unauthorized Access");
+
+ var ticket = new AuthenticationTicket(principal, Schemes.GoogleScheme);
+ return AuthenticateResult.Success(ticket);
+ }
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Helpers/GoogleAuthHelper.cs b/Core.Thalos.BuildingBlocks/Authentication/Helpers/GoogleAuthHelper.cs
new file mode 100644
index 0000000..9470ef2
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Helpers/GoogleAuthHelper.cs
@@ -0,0 +1,31 @@
+using Google.Apis.Auth.OAuth2;
+using Google.Apis.Oauth2.v2;
+using Microsoft.Extensions.Configuration;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Helpers
+{
+ public class GoogleAuthHelper(IConfiguration config) : IGoogleAuthHelper
+ {
+ public ClientSecrets GetClientSecrets()
+ {
+ string clientId = config["Authentication:Google:ClientId"]!;
+ string clientSecret = config["Authentication:Google:ClientSecret"]!;
+
+ return new() { ClientId = clientId, ClientSecret = clientSecret };
+ }
+
+ public string[] GetScopes()
+ {
+ var scopes = new[]
+ {
+ Oauth2Service.Scope.Openid,
+ Oauth2Service.Scope.UserinfoEmail,
+ Oauth2Service.Scope.UserinfoProfile
+ };
+
+ return scopes;
+ }
+
+ public string ScopeToString() => string.Join(", ", GetScopes());
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Authentication/Helpers/IGoogleAuthHelper.cs b/Core.Thalos.BuildingBlocks/Authentication/Helpers/IGoogleAuthHelper.cs
new file mode 100644
index 0000000..cac6350
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Authentication/Helpers/IGoogleAuthHelper.cs
@@ -0,0 +1,11 @@
+using Google.Apis.Auth.OAuth2;
+
+namespace Core.Thalos.BuildingBlocks.Authentication.Helpers
+{
+ public interface IGoogleAuthHelper
+ {
+ string[] GetScopes();
+ string ScopeToString();
+ ClientSecrets GetClientSecrets();
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Common/Constants/Policies.cs b/Core.Thalos.BuildingBlocks/Common/Constants/Policies.cs
new file mode 100644
index 0000000..aa7c248
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Common/Constants/Policies.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+namespace Core.Thalos.BuildingBlocks.Common.Constants
+{
+ ///
+ /// Constants for policy.
+ ///
+ public class Policies
+ {
+ ///
+ /// Defines the access policy for reading mobile-related data.
+ /// This policy grants read-only permissions for retrieving mobile device information,
+ /// user mobile settings, or related data as per the application's authorization scope.
+ ///
+ public const string Read = "Read";
+
+ ///
+ /// Defines the access policy for writing mobile-related data.
+ /// This policy grants permissions to modify, update, or store mobile device information,
+ /// user mobile settings, or related data as per the application's authorization scope.
+ ///
+ public const string Write = "Write";
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Common/Constants/Roles.cs b/Core.Thalos.BuildingBlocks/Common/Constants/Roles.cs
new file mode 100644
index 0000000..023b651
--- /dev/null
+++ b/Core.Thalos.BuildingBlocks/Common/Constants/Roles.cs
@@ -0,0 +1,15 @@
+namespace Core.Thalos.BuildingBlocks.Common.Constants
+{
+ public class Roles
+ {
+ ///
+ /// The role for Guest.
+ ///
+ public const string Guest = "684909c4826cd093b4f61c11";
+
+ ///
+ /// The role for Admin.
+ ///
+ public const string Admin = "68407642ec46a0e6fe1e8ec9";
+ }
+}
diff --git a/Core.Thalos.BuildingBlocks/Common/Constants/Schemes.cs b/Core.Thalos.BuildingBlocks/Common/Constants/Schemes.cs
index b0e176b..268a42a 100644
--- a/Core.Thalos.BuildingBlocks/Common/Constants/Schemes.cs
+++ b/Core.Thalos.BuildingBlocks/Common/Constants/Schemes.cs
@@ -14,5 +14,10 @@
/// The azure scheme.
///
public const string AzureScheme = "AzureScheme";
+
+ ///
+ /// The google scheme.
+ ///
+ public const string GoogleScheme = "GoogleScheme";
}
}
diff --git a/Core.Thalos.BuildingBlocks/Core.Thalos.BuildingBlocks.csproj b/Core.Thalos.BuildingBlocks/Core.Thalos.BuildingBlocks.csproj
index d947258..cf01555 100644
--- a/Core.Thalos.BuildingBlocks/Core.Thalos.BuildingBlocks.csproj
+++ b/Core.Thalos.BuildingBlocks/Core.Thalos.BuildingBlocks.csproj
@@ -8,13 +8,16 @@
net8.0
enable
enable
- 1.0.2
+ 1.0.5
$(Date:yyyyMMddHHmm)
+
+
+