Skip to content

Commit d3a290a

Browse files
feat(auth): enables OIDC auth code flow (#299)
* Enable OIDC auth code flow * Changes requested by hiranya911 * More changes requested by hiranya911 * Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs Co-authored-by: Kevin Cheung <[email protected]> * Update FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs Co-authored-by: Kevin Cheung <[email protected]> * Fixing tests * Fixing tests Co-authored-by: Kevin Cheung <[email protected]>
1 parent d422935 commit d3a290a

File tree

5 files changed

+173
-4
lines changed

5 files changed

+173
-4
lines changed

FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/AbstractOidcProviderConfigTest.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ public void CreateProviderConfig()
4646
Assert.True(config.Enabled);
4747
Assert.Equal("OIDC_CLIENT_ID", config.ClientId);
4848
Assert.Equal("https://oidc.com/issuer", config.Issuer);
49+
Assert.Equal("OIDC_CLIENT_SECRET", config.ClientSecret);
50+
Assert.True(config.CodeResponseType);
51+
Assert.False(config.IdTokenResponseType);
4952
}
5053

5154
[Fact]
@@ -59,6 +62,9 @@ public async Task GetProviderConfig()
5962
Assert.True(config.Enabled);
6063
Assert.Equal("OIDC_CLIENT_ID", config.ClientId);
6164
Assert.Equal("https://oidc.com/issuer", config.Issuer);
65+
Assert.Equal("OIDC_CLIENT_SECRET", config.ClientSecret);
66+
Assert.True(config.CodeResponseType);
67+
Assert.False(config.IdTokenResponseType);
6268
}
6369

6470
[Fact]
@@ -84,6 +90,9 @@ public async Task ListProviderConfig()
8490
Assert.True(config.Enabled);
8591
Assert.Equal("OIDC_CLIENT_ID", config.ClientId);
8692
Assert.Equal("https://oidc.com/issuer", config.Issuer);
93+
Assert.Equal("OIDC_CLIENT_SECRET", config.ClientSecret);
94+
Assert.True(config.CodeResponseType);
95+
Assert.False(config.IdTokenResponseType);
8796
}
8897

8998
[Fact]
@@ -97,6 +106,9 @@ public async Task UpdateProviderConfig()
97106
Enabled = false,
98107
ClientId = "UPDATED_OIDC_CLIENT_ID",
99108
Issuer = "https://oidc.com/updated-issuer",
109+
ClientSecret = "UPDATED_OIDC_CLIENT_SECRET",
110+
CodeResponseType = false,
111+
IdTokenResponseType = true,
100112
};
101113

102114
var config = await this.auth.UpdateProviderConfigAsync(args);
@@ -106,6 +118,9 @@ public async Task UpdateProviderConfig()
106118
Assert.False(config.Enabled);
107119
Assert.Equal("UPDATED_OIDC_CLIENT_ID", config.ClientId);
108120
Assert.Equal("https://oidc.com/updated-issuer", config.Issuer);
121+
Assert.Equal("UPDATED_OIDC_CLIENT_SECRET", config.ClientSecret);
122+
Assert.False(config.CodeResponseType);
123+
Assert.True(config.IdTokenResponseType);
109124
}
110125

111126
[Fact]

FirebaseAdmin/FirebaseAdmin.IntegrationTests/Auth/OidcProviderConfigFixture.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public OidcProviderConfigFixture()
3131
Enabled = true,
3232
ClientId = "OIDC_CLIENT_ID",
3333
Issuer = "https://oidc.com/issuer",
34+
ClientSecret = "OIDC_CLIENT_SECRET",
35+
CodeResponseType = true,
36+
IdTokenResponseType = false,
3437
};
3538
this.ProviderConfig = this.Auth.CreateProviderConfigAsync(args).Result;
3639
}

FirebaseAdmin/FirebaseAdmin.Tests/Auth/Providers/OidcProviderConfigTest.cs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ public class OidcProviderConfigTest
3333
""clientId"": ""CLIENT_ID"",
3434
""issuer"": ""https://oidc.com/issuer"",
3535
""displayName"": ""oidcProviderName"",
36-
""enabled"": true
36+
""enabled"": true,
37+
""clientSecret"": ""CLIENT_SECRET"",
38+
""responseType"": {
39+
""code"": true,
40+
""idToken"": true
41+
}
3742
}";
3843

3944
private static readonly IList<string> ListConfigsResponses = new List<string>()
@@ -135,6 +140,9 @@ public async Task CreateConfig(ProviderTestConfig config)
135140
Enabled = true,
136141
ClientId = "CLIENT_ID",
137142
Issuer = "https://oidc.com/issuer",
143+
ClientSecret = "CLIENT_SECRET",
144+
CodeResponseType = true,
145+
IdTokenResponseType = true,
138146
};
139147

140148
var provider = await auth.CreateProviderConfigAsync(args);
@@ -147,11 +155,14 @@ public async Task CreateConfig(ProviderTestConfig config)
147155

148156
var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
149157
handler.LastRequestBody);
150-
Assert.Equal(4, body.Count);
158+
Assert.Equal(6, body.Count);
151159
Assert.Equal("oidcProviderName", body["displayName"]);
152160
Assert.True((bool)body["enabled"]);
153161
Assert.Equal("CLIENT_ID", body["clientId"]);
154162
Assert.Equal("https://oidc.com/issuer", body["issuer"]);
163+
Assert.Equal("CLIENT_SECRET", body["clientSecret"]);
164+
Assert.True((bool)body["responseType"]["code"]);
165+
Assert.True((bool)body["responseType"]["idToken"]);
155166
}
156167

157168
[Theory]
@@ -252,6 +263,9 @@ public async Task UpdateConfig(ProviderTestConfig config)
252263
Enabled = true,
253264
ClientId = "CLIENT_ID",
254265
Issuer = "https://oidc.com/issuer",
266+
ClientSecret = "CLIENT_SECRET",
267+
CodeResponseType = true,
268+
IdTokenResponseType = true,
255269
};
256270

257271
var provider = await auth.UpdateProviderConfigAsync(args);
@@ -260,16 +274,19 @@ public async Task UpdateConfig(ProviderTestConfig config)
260274
Assert.Equal(1, handler.Requests.Count);
261275
var request = handler.Requests[0];
262276
Assert.Equal(ProviderTestConfig.PatchMethod, request.Method);
263-
var mask = "clientId,displayName,enabled,issuer";
277+
var mask = "clientId,clientSecret,displayName,enabled,issuer,responseType.code,responseType.idToken";
264278
config.AssertRequest($"oauthIdpConfigs/oidc.provider?updateMask={mask}", request);
265279

266280
var body = NewtonsoftJsonSerializer.Instance.Deserialize<JObject>(
267281
handler.LastRequestBody);
268-
Assert.Equal(4, body.Count);
282+
Assert.Equal(6, body.Count);
269283
Assert.Equal("oidcProviderName", body["displayName"]);
270284
Assert.True((bool)body["enabled"]);
271285
Assert.Equal("CLIENT_ID", body["clientId"]);
272286
Assert.Equal("https://oidc.com/issuer", body["issuer"]);
287+
Assert.Equal("CLIENT_SECRET", body["clientSecret"]);
288+
Assert.True((bool)body["responseType"]["code"]);
289+
Assert.True((bool)body["responseType"]["idToken"]);
273290
}
274291

275292
[Theory]
@@ -621,6 +638,9 @@ private void AssertOidcProviderConfig(OidcProviderConfig provider)
621638
Assert.True(provider.Enabled);
622639
Assert.Equal("CLIENT_ID", provider.ClientId);
623640
Assert.Equal("https://oidc.com/issuer", provider.Issuer);
641+
Assert.Equal("CLIENT_SECRET", provider.ClientSecret);
642+
Assert.True(provider.CodeResponseType);
643+
Assert.True(provider.IdTokenResponseType);
624644
}
625645

626646
public class InvalidCreateArgs : IEnumerable<object[]>
@@ -682,6 +702,29 @@ public IEnumerator<object[]> MakeEnumerator()
682702
},
683703
"Malformed issuer string: not a url",
684704
};
705+
yield return new object[]
706+
{
707+
new OidcProviderConfigArgs()
708+
{
709+
ProviderId = "oidc.provider",
710+
ClientId = "CLIENT_ID",
711+
Issuer = "https://oidc.com/issuer",
712+
CodeResponseType = true,
713+
},
714+
"Client secret must not be null or empty for code response type.",
715+
};
716+
yield return new object[]
717+
{
718+
new OidcProviderConfigArgs()
719+
{
720+
ProviderId = "oidc.provider",
721+
ClientId = "CLIENT_ID",
722+
Issuer = "https://oidc.com/issuer",
723+
CodeResponseType = false,
724+
IdTokenResponseType = false,
725+
},
726+
"At least one response type must be returned.",
727+
};
685728
}
686729

687730
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
@@ -754,6 +797,27 @@ public IEnumerator<object[]> MakeEnumerator()
754797
},
755798
"Malformed issuer string: not a url",
756799
};
800+
yield return new object[]
801+
{
802+
new OidcProviderConfigArgs()
803+
{
804+
ProviderId = "oidc.provider",
805+
Issuer = "https://oidc.com/issuer",
806+
CodeResponseType = true,
807+
},
808+
"Client secret must not be null or empty for code response type.",
809+
};
810+
yield return new object[]
811+
{
812+
new OidcProviderConfigArgs()
813+
{
814+
ProviderId = "oidc.provider",
815+
Issuer = "https://oidc.com/issuer",
816+
CodeResponseType = false,
817+
IdTokenResponseType = false,
818+
},
819+
"At least one response type must be returned.",
820+
};
757821
}
758822

759823
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfig.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ internal OidcProviderConfig(Request request)
2727
{
2828
this.ClientId = request.ClientId;
2929
this.Issuer = request.Issuer;
30+
this.ClientSecret = request.ClientSecret;
31+
this.IdTokenResponseType = request.ResponseType.IdToken == true;
32+
this.CodeResponseType = request.ResponseType.Code == true;
3033
}
3134

3235
/// <summary>
@@ -63,13 +66,43 @@ internal OidcProviderConfig(Request request)
6366
/// </summary>
6467
public string Issuer { get; }
6568

69+
/// <summary>
70+
/// Gets the client secret, which is used to verify Code response types.
71+
/// </summary>
72+
public string ClientSecret { get; }
73+
74+
/// <summary>
75+
/// Gets a value indicating whether an ID Token response type will be provided.
76+
/// </summary>
77+
public bool IdTokenResponseType { get; }
78+
79+
/// <summary>
80+
/// Gets a value indicating whether an Code type response type will be provided.
81+
/// </summary>
82+
public bool CodeResponseType { get; }
83+
84+
internal sealed class ResponseTypeInfo
85+
{
86+
[JsonProperty("code")]
87+
internal bool? Code { get; set; }
88+
89+
[JsonProperty("idToken")]
90+
internal bool? IdToken { get; set; }
91+
}
92+
6693
internal sealed new class Request : AuthProviderConfig.Request
6794
{
6895
[JsonProperty("clientId")]
6996
internal string ClientId { get; set; }
7097

7198
[JsonProperty("issuer")]
7299
internal string Issuer { get; set; }
100+
101+
[JsonProperty("clientSecret")]
102+
internal string ClientSecret { get; set; }
103+
104+
[JsonProperty("responseType")]
105+
internal ResponseTypeInfo ResponseType { get; set; }
73106
}
74107
}
75108
}

FirebaseAdmin/FirebaseAdmin/Auth/Providers/OidcProviderConfigArgs.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ public sealed class OidcProviderConfigArgs : AuthProviderConfigArgs<OidcProvider
5656
/// </summary>
5757
public string Issuer { get; set; }
5858

59+
/// <summary>
60+
/// Gets or sets the client secret, which is used to verify Code response types.
61+
/// </summary>
62+
public string ClientSecret { get; set; }
63+
64+
/// <summary>
65+
/// Gets or sets a value indicating whether this OIDC provider uses an ID Token response type.
66+
/// </summary>
67+
public bool? IdTokenResponseType { get; set; }
68+
69+
/// <summary>
70+
/// Gets or sets a value indicating whether this OIDC provider uses a Code response type.
71+
/// </summary>
72+
public bool? CodeResponseType { get; set; }
73+
5974
internal override AuthProviderConfig.Request ToCreateRequest()
6075
{
6176
var req = new OidcProviderConfig.Request()
@@ -64,7 +79,17 @@ internal override AuthProviderConfig.Request ToCreateRequest()
6479
Enabled = this.Enabled,
6580
ClientId = this.ClientId,
6681
Issuer = this.Issuer,
82+
ClientSecret = this.ClientSecret,
6783
};
84+
if (this.CodeResponseType != null || this.IdTokenResponseType != null)
85+
{
86+
req.ResponseType = new OidcProviderConfig.ResponseTypeInfo()
87+
{
88+
Code = this.CodeResponseType,
89+
IdToken = this.IdTokenResponseType,
90+
};
91+
}
92+
6893
if (string.IsNullOrEmpty(req.ClientId))
6994
{
7095
throw new ArgumentException("Client ID must not be null or empty.");
@@ -79,6 +104,16 @@ internal override AuthProviderConfig.Request ToCreateRequest()
79104
throw new ArgumentException($"Malformed issuer string: {req.Issuer}");
80105
}
81106

107+
if (req.ResponseType?.Code == true && string.IsNullOrEmpty(req.ClientSecret))
108+
{
109+
throw new ArgumentException("Client secret must not be null or empty for code response type.");
110+
}
111+
112+
if (req.ResponseType?.Code == false && req.ResponseType?.IdToken == false)
113+
{
114+
throw new ArgumentException("At least one response type must be returned.");
115+
}
116+
82117
return req;
83118
}
84119

@@ -90,7 +125,16 @@ internal override AuthProviderConfig.Request ToUpdateRequest()
90125
Enabled = this.Enabled,
91126
ClientId = this.ClientId,
92127
Issuer = this.Issuer,
128+
ClientSecret = this.ClientSecret,
93129
};
130+
if (this.CodeResponseType != null || this.IdTokenResponseType != null)
131+
{
132+
req.ResponseType = new OidcProviderConfig.ResponseTypeInfo()
133+
{
134+
Code = this.CodeResponseType,
135+
IdToken = this.IdTokenResponseType,
136+
};
137+
}
94138

95139
if (req.ClientId == string.Empty)
96140
{
@@ -106,6 +150,16 @@ internal override AuthProviderConfig.Request ToUpdateRequest()
106150
throw new ArgumentException($"Malformed issuer string: {req.Issuer}");
107151
}
108152

153+
if (req.ResponseType?.Code == true && string.IsNullOrEmpty(req.ClientSecret))
154+
{
155+
throw new ArgumentException("Client secret must not be null or empty for code response type.");
156+
}
157+
158+
if (req.ResponseType?.Code == false && req.ResponseType?.IdToken == false)
159+
{
160+
throw new ArgumentException("At least one response type must be returned.");
161+
}
162+
109163
return req;
110164
}
111165

0 commit comments

Comments
 (0)