diff --git a/docs/migration/contract-extraction-map.md b/docs/migration/contract-extraction-map.md
index a1a120e..e0860fa 100644
--- a/docs/migration/contract-extraction-map.md
+++ b/docs/migration/contract-extraction-map.md
@@ -8,6 +8,7 @@
| thalos-service policy contracts | Contracts/Policies | Preserve policy semantics and required fields |
| thalos-service policy context contracts | Contracts/Context | Keep field naming stable for compatibility window |
| thalos-bff refresh session contracts | Contracts/Sessions | Candidate for shared capability standardization |
+| provider flow metadata (JWT/Azure/Google) | Contracts/Conventions | Provider metadata stays transport-neutral and additive |
## Namespace Strategy
- Current Thalos namespaces are mapped to `BuildingBlock.Identity.Contracts.*`.
@@ -18,3 +19,4 @@
2. Add compatibility bridge in Thalos consumers.
3. Migrate service consumers first, then BFF consumers.
4. Deprecate old namespace usage after compatibility window.
+5. Keep provider enum and provider-specific fields additive to avoid breaking consumers.
diff --git a/src/BuildingBlock.Identity.Contracts/Conventions/IdentityAuthProvider.cs b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityAuthProvider.cs
new file mode 100644
index 0000000..6d4001b
--- /dev/null
+++ b/src/BuildingBlock.Identity.Contracts/Conventions/IdentityAuthProvider.cs
@@ -0,0 +1,22 @@
+namespace BuildingBlock.Identity.Contracts.Conventions;
+
+///
+/// Supported identity authentication providers.
+///
+public enum IdentityAuthProvider
+{
+ ///
+ /// AgileWebs-issued internal JWT flow.
+ ///
+ InternalJwt = 0,
+
+ ///
+ /// Microsoft Entra ID / Azure AD OAuth/OIDC flow.
+ ///
+ AzureAd = 1,
+
+ ///
+ /// Google OAuth/OIDC flow.
+ ///
+ Google = 2
+}
diff --git a/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs b/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs
index d4416e2..d508935 100644
--- a/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs
+++ b/src/BuildingBlock.Identity.Contracts/Grpc/IdentityPolicyGrpcContract.cs
@@ -6,4 +6,9 @@ namespace BuildingBlock.Identity.Contracts.Grpc;
/// Identity subject identifier.
/// Tenant identifier.
/// Permission code.
-public sealed record IdentityPolicyGrpcContract(string SubjectId, string TenantId, string PermissionCode);
+/// Auth provider.
+public sealed record IdentityPolicyGrpcContract(
+ string SubjectId,
+ string TenantId,
+ string PermissionCode,
+ string Provider = "InternalJwt");
diff --git a/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs
index 078c350..f60ad23 100644
--- a/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs
+++ b/src/BuildingBlock.Identity.Contracts/Requests/EvaluateIdentityPolicyRequest.cs
@@ -1,4 +1,5 @@
using BuildingBlock.Identity.Contracts.Abstractions;
+using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
@@ -8,5 +9,10 @@ namespace BuildingBlock.Identity.Contracts.Requests;
/// Identity subject identifier.
/// Tenant identifier.
/// Permission code to evaluate.
-public sealed record EvaluateIdentityPolicyRequest(string SubjectId, string TenantId, string PermissionCode)
+/// Auth provider used for the request.
+public sealed record EvaluateIdentityPolicyRequest(
+ string SubjectId,
+ string TenantId,
+ string PermissionCode,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt)
: IIdentityCapabilityContract;
diff --git a/src/BuildingBlock.Identity.Contracts/Requests/ExchangeIdentityProviderTokenRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/ExchangeIdentityProviderTokenRequest.cs
new file mode 100644
index 0000000..8b992f5
--- /dev/null
+++ b/src/BuildingBlock.Identity.Contracts/Requests/ExchangeIdentityProviderTokenRequest.cs
@@ -0,0 +1,17 @@
+using BuildingBlock.Identity.Contracts.Abstractions;
+using BuildingBlock.Identity.Contracts.Conventions;
+
+namespace BuildingBlock.Identity.Contracts.Requests;
+
+///
+/// Requests identity provider token exchange for a subject token.
+///
+/// Tenant identifier.
+/// External identity provider.
+/// Provider-issued token (id/access token).
+/// Correlation identifier.
+public sealed record ExchangeIdentityProviderTokenRequest(
+ string TenantId,
+ IdentityAuthProvider Provider,
+ string ExternalToken,
+ string CorrelationId = "") : IIdentityCapabilityContract;
diff --git a/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs
index 60a2515..7ab75d5 100644
--- a/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs
+++ b/src/BuildingBlock.Identity.Contracts/Requests/IdentityPolicyContextRequest.cs
@@ -1,4 +1,5 @@
using BuildingBlock.Identity.Contracts.Abstractions;
+using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
@@ -8,5 +9,10 @@ namespace BuildingBlock.Identity.Contracts.Requests;
/// Identity subject identifier.
/// Tenant identifier.
/// Permission code to evaluate.
-public sealed record IdentityPolicyContextRequest(string SubjectId, string TenantId, string PermissionCode)
+/// Auth provider used for the request.
+public sealed record IdentityPolicyContextRequest(
+ string SubjectId,
+ string TenantId,
+ string PermissionCode,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt)
: IIdentityCapabilityContract;
diff --git a/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs
index 23d5b4e..7154b43 100644
--- a/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs
+++ b/src/BuildingBlock.Identity.Contracts/Requests/IssueIdentityTokenRequest.cs
@@ -1,4 +1,5 @@
using BuildingBlock.Identity.Contracts.Abstractions;
+using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
@@ -7,4 +8,10 @@ namespace BuildingBlock.Identity.Contracts.Requests;
///
/// Identity subject identifier.
/// Tenant identifier.
-public sealed record IssueIdentityTokenRequest(string SubjectId, string TenantId) : IIdentityCapabilityContract;
+/// Auth provider used for the request.
+/// External provider token when applicable.
+public sealed record IssueIdentityTokenRequest(
+ string SubjectId,
+ string TenantId,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt,
+ string ExternalToken = "") : IIdentityCapabilityContract;
diff --git a/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs b/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs
index d0a1617..9050aae 100644
--- a/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs
+++ b/src/BuildingBlock.Identity.Contracts/Requests/RefreshIdentitySessionRequest.cs
@@ -1,4 +1,5 @@
using BuildingBlock.Identity.Contracts.Abstractions;
+using BuildingBlock.Identity.Contracts.Conventions;
namespace BuildingBlock.Identity.Contracts.Requests;
@@ -7,5 +8,9 @@ namespace BuildingBlock.Identity.Contracts.Requests;
///
/// Refresh token value.
/// Correlation identifier.
-public sealed record RefreshIdentitySessionRequest(string RefreshToken, string CorrelationId)
+/// Auth provider used for the request.
+public sealed record RefreshIdentitySessionRequest(
+ string RefreshToken,
+ string CorrelationId,
+ IdentityAuthProvider Provider = IdentityAuthProvider.InternalJwt)
: IIdentityCapabilityContract;
diff --git a/src/BuildingBlock.Identity.Contracts/Responses/ExchangeIdentityProviderTokenResponse.cs b/src/BuildingBlock.Identity.Contracts/Responses/ExchangeIdentityProviderTokenResponse.cs
new file mode 100644
index 0000000..3ebee51
--- /dev/null
+++ b/src/BuildingBlock.Identity.Contracts/Responses/ExchangeIdentityProviderTokenResponse.cs
@@ -0,0 +1,17 @@
+using BuildingBlock.Identity.Contracts.Abstractions;
+using BuildingBlock.Identity.Contracts.Conventions;
+
+namespace BuildingBlock.Identity.Contracts.Responses;
+
+///
+/// Returns token exchange result from an external provider token.
+///
+/// Resolved identity subject identifier.
+/// Tenant identifier.
+/// External identity provider.
+/// Whether provider token was accepted.
+public sealed record ExchangeIdentityProviderTokenResponse(
+ string SubjectId,
+ string TenantId,
+ IdentityAuthProvider Provider,
+ bool IsAuthenticated) : IIdentityCapabilityContract;
diff --git a/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs b/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs
index 898e31d..fa0ab12 100644
--- a/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs
+++ b/tests/BuildingBlock.Identity.Contracts.UnitTests/ContractShapeTests.cs
@@ -19,12 +19,33 @@ public class ContractShapeTests
[Fact]
public void IdentityContracts_WhenInstantiated_ImplementMarkerInterface()
{
- IIdentityCapabilityContract issueRequest = new IssueIdentityTokenRequest("subject-a", "tenant-a");
+ IIdentityCapabilityContract issueRequest = new IssueIdentityTokenRequest(
+ "subject-a",
+ "tenant-a",
+ IdentityAuthProvider.AzureAd,
+ "external-token");
IIdentityCapabilityContract issueResponse = new IssueIdentityTokenResponse("token-a", 1800);
- IIdentityCapabilityContract policyRequest = new EvaluateIdentityPolicyRequest("subject-b", "tenant-b", "identity.token.issue");
+ IIdentityCapabilityContract policyRequest = new EvaluateIdentityPolicyRequest(
+ "subject-b",
+ "tenant-b",
+ "identity.token.issue",
+ IdentityAuthProvider.Google);
IIdentityCapabilityContract policyResponse = new EvaluateIdentityPolicyResponse("subject-b", "identity.token.issue", true);
- IIdentityCapabilityContract refreshRequest = new RefreshIdentitySessionRequest("refresh-a", "corr-refresh");
+ IIdentityCapabilityContract refreshRequest = new RefreshIdentitySessionRequest(
+ "refresh-a",
+ "corr-refresh",
+ IdentityAuthProvider.InternalJwt);
IIdentityCapabilityContract refreshResponse = new RefreshIdentitySessionResponse("token-b", 900);
+ IIdentityCapabilityContract exchangeRequest = new ExchangeIdentityProviderTokenRequest(
+ "tenant-c",
+ IdentityAuthProvider.AzureAd,
+ "provider-token",
+ "corr-exchange");
+ IIdentityCapabilityContract exchangeResponse = new ExchangeIdentityProviderTokenResponse(
+ "subject-c",
+ "tenant-c",
+ IdentityAuthProvider.AzureAd,
+ true);
Assert.NotNull(issueRequest);
Assert.NotNull(issueResponse);
@@ -32,5 +53,20 @@ public class ContractShapeTests
Assert.NotNull(policyResponse);
Assert.NotNull(refreshRequest);
Assert.NotNull(refreshResponse);
+ Assert.NotNull(exchangeRequest);
+ Assert.NotNull(exchangeResponse);
+ }
+
+ [Fact]
+ public void IssueIdentityTokenRequest_WhenProviderSpecified_PreservesProviderMetadata()
+ {
+ var request = new IssueIdentityTokenRequest(
+ "subject-1",
+ "tenant-1",
+ IdentityAuthProvider.Google,
+ "google-token");
+
+ Assert.Equal(IdentityAuthProvider.Google, request.Provider);
+ Assert.Equal("google-token", request.ExternalToken);
}
}