Skip to content

Commit

Permalink
Fix: Handling of Multiple Scopes in Okta .NET SDK v9 (#767)
Browse files Browse the repository at this point in the history
  • Loading branch information
aniket-okta authored Jan 17, 2025
1 parent 796663b commit dbf77e9
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 5 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog
Running changelog of releases since `3.1.1`

## 9.0.4
### Fixed
- Handling of Multiple Scopes in Okta .NET SDK v9 (#753)

## 9.0.0
### Fixed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ var configuration = new Configuration
OktaDomain = "https://{{yourOktaDomain}}",
AuthorizationMode = AuthorizationMode.PrivateKey,
ClientId = "{{clientId}}",
Scopes = new List<string> { "okta.users.read", "okta.apps.read" }, // Add all the scopes you need
Scopes = new HashSet<string> { "okta.users.read", "okta.apps.read" }, // Add all the scopes you need
PrivateKey = privateKey
};

Expand Down
2 changes: 1 addition & 1 deletion openapi3/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"packageName" : "Okta.Sdk",
"outputDir" : "../",
"inputSpec" : "./management.yaml",
"packageVersion" : "9.0.3",
"packageVersion" : "9.0.4",
"packageDescription" : "Official .NET SDK for the Okta API",
"packageTitle" : "Official .NET SDK for the Okta API",
"packageCompany" : "Okta, Inc.",
Expand Down
2 changes: 1 addition & 1 deletion openapi3/templates/OAuthApi.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ namespace {{packageName}}.{{apiPackage}}

_dpopProofJwtGenerator.RotateKeys();
var jwtSecurityToken = _clientAssertionJwtGenerator.GenerateJwt();
var scopes = string.Join("+", Configuration.Scopes);
var scopes = string.Join(" ", Configuration.Scopes);
var accessTokenUri = "/oauth2/v1/token";

localVarRequestOptions.FormParameters.Add("grant_type", "client_credentials");
Expand Down
101 changes: 101 additions & 0 deletions src/Okta.Sdk.IntegrationTest/OAuthScenarios.cs
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,108 @@ public async Task ListUsersWithPaginationAndRetry()
}
}

[Fact]
public async Task RetrieveAccessTokenWithMultipleScopes()
{
var guid = Guid.NewGuid();
var payload = $@"{{
""client_name"": ""dotnet-sdk: Service Client {guid}"",
""response_types"": [
""token""
],
""grant_types"": [
""client_credentials""
],
""token_endpoint_auth_method"": ""private_key_jwt"",
""application_type"": ""service"",
""jwks"": {{
""keys"": [
{{
""kty"":""RSA"",
""e"":""AQAB"",
""n"":""mTjMc8AxU102LT1Jf-1qkGmaSiK4L7DDlC1SMvtyCRbDaiJDIagedfp1w8Pgud8YWOaS5FFx0S6JqGGP2U8OtpowzBcv5sYa-e5LHfnoueTJPj_jnI3fj5omZM1w-ofhFLPZoYEQ7DFYw0yLrzf8zaKB5-9BZ8yyOLhSKqxaOl2s7lw2TrwBRuQpPXmEir70oDPvazd8-An5ow6F5q7mzMtHAt61DJqrosRHiRwh4N37zIX_RNu-Tn1aMktCBl01rdoDyVq7Y4iwNH8ZAtT5thKK2eo8d-jb9TF9PH6LGffYCth157w-K4AZwXw74Ybo5NOux3XpIpKRbFTwvBLp1Q""
}}
]
}}
}}";

var oktaDomain = Configuration.GetConfigurationOrDefault().OktaDomain;

// Remove '/' at the end since endpoint fails otherwise
if (oktaDomain.EndsWith($"/"))
{
oktaDomain = oktaDomain.Remove(oktaDomain.Length - 1);
}

var apiClient = new ApiClient(oktaDomain);

// Create grant payloads for "users.read" and "apps.read" scopes
var usersReadGrantPayload = $@"{{
""scopeId"" : ""okta.users.read"",
""issuer"" : ""{oktaDomain}""
}}";
var appsReadGrantPayload = $@"{{
""scopeId"" : ""okta.apps.read"",
""issuer"" : ""{oktaDomain}""
}}";

var requestOptions = GetBasicRequestOptions();
requestOptions.Data = JObject.Parse(payload);

var serviceResponse = await apiClient.PostAsync<JObject>("/oauth2/v1/clients", requestOptions);
output.WriteLine("Create client response {0}", serviceResponse.Data.ToString());
var clientId = serviceResponse.Data["client_id"].ToString();

try
{
// Assign "users.read" and "apps.read" scope grant to the created client
requestOptions = GetBasicRequestOptions();
requestOptions.Data = JObject.Parse(usersReadGrantPayload);
var usersReadGrantResponse = await apiClient.PostAsync<JObject>($"/api/v1/apps/{clientId}/grants", requestOptions);

requestOptions = GetBasicRequestOptions();
requestOptions.Data = JObject.Parse(appsReadGrantPayload);
var appsReadGrantResponse = await apiClient.PostAsync<JObject>($"/api/v1/apps/{clientId}/grants", requestOptions);

//Ensure the "users.read" and "apps.read" scopes grant was successfully assigned
if(usersReadGrantResponse.StatusCode != System.Net.HttpStatusCode.OK || appsReadGrantResponse.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception($"Failed to assign scopes to client. Status Code: UsersReadGrantResponse-{usersReadGrantResponse.StatusCode}, AppsReadGrantResponse-{appsReadGrantResponse.StatusCode}");
}

//Private key used for the authentication
var jsonPrivateKey = @"{
""p"":""2-8pgwYv9jrkM2KsbnQmnJZnr69Rsj95M20I1zx5HhM3tgjGSa7d_dELPRkp9Usy8UGISt7eUHpYOVl529irHwbXevuId1Q804aQ_AtNJwpbRY48rw2T8LdtyVSaEyoFMCa8PJwtzZYzKJCKAe5eoXvW5zxB65RaIct0igYcoIs"",
""kty"":""RSA"",
""q"":""slkNUY_SCIn95ip7HoPs_IIiNoKOGeesIV1gacyoAycly1vBPMhtar9gwx51nN1tCMVGlSOk583eRJe2omAbqkIEYm1jSWtMdJKQSOJvx812xbF1afMgJDlJ6iRIlcnWEYhNNMCK5s_UR5zE0Mc5jktxDFeiEatriyu6o9hQix8"",
""d"":""LIpJTKCi9hPTiuUU954hayd3lXNwTVS6Fdny2iUj6iZ22eRp1V_UswECuMy5B-8lWbp1Gu_eASvhElSCB26m3UgHRVy8LP6Lmvm9VlJuZ5NtOK5D0R-gzFLINGdQH1PehzEc44jsTWyu297lgCLrVy-VScHQJodni3txTzxY4jwjILMfLB7OWdKVkvDQ4g70BYTVN5kZKjA9B0lLsofi1gUY_EVlojuvSKbm3HY0JR_JThtEd_nZw_tXTYmlP58plVYX-9JnA8NcFRB6dUNO7XqcXU1SafWqoM9yam1nGSMYRknwjSSTKRdBXHSy7PVxVHhpC72wb3aWNsOqWNo0ZQ"",
""e"":""AQAB"",
""qi"":""u1mS53N4BUSoMeOHFd0o4_n3QGH4izNUsiJVBIkv_UZUAk4LYudPEikTWRLqWsrcXmOyZYao5sSaZyt-B2XCkfdnkIhl-Q7b0_W0yt3Eh5XjAzH9oy5Dklog255lh-Y0yoWXvLjq-KEDs7Nd2uIT4gvKU4ymTqybvzazr2jY9qQ"",""dp"":""nCtPBsK1-9oFgJdoaWYApOAH8DBFipSXs3SQ-oTuW_S5coD4jAmniDuQB2p-6LblDXrDFKb8pZi6XL60UO-hUv7As4s4c8NVDb5X5SEBP9-Sv-koHgU-L4eQZY21ejY0SOS4dTFRNNKasQsxc_2XJIOTLc8T3_wPpD-cGQYN_dE"",""dq"":""ZWb4iZ0qICzFLW6N3gXIYrFi3ndQcC4m0jmTLdRs2o4RkRQ0RGj4vS7ex1G0MWI8MjZoMTe49Qs6Cunvr1bRo_YxI_1p7D6Tk9wZKTeFsqaBl1mUlo7jgXUJL5U9p9zAV-uVah7nWuBjo-vgg4wij2MZfZj9zuoWFWThk3LUKKU"",""n"":""mTjMc8AxU102LT1Jf-1qkGmaSiK4L7DDlC1SMvtyCRbDaiJDIagedfp1w8Pgud8YWOaS5FFx0S6JqGGP2U8OtpowzBcv5sYa-e5LHfnoueTJPj_jnI3fj5omZM1w-ofhFLPZoYEQ7DFYw0yLrzf8zaKB5-9BZ8yyOLhSKqxaOl2s7lw2TrwBRuQpPXmEir70oDPvazd8-An5ow6F5q7mzMtHAt61DJqrosRHiRwh4N37zIX_RNu-Tn1aMktCBl01rdoDyVq7Y4iwNH8ZAtT5thKK2eo8d-jb9TF9PH6LGffYCth157w-K4AZwXw74Ybo5NOux3XpIpKRbFTwvBLp1Q""
}";

//Configure the OAuth settings for token retrieval
var configuration = new Configuration();
configuration.Scopes = new HashSet<string> { "okta.users.read", "okta.apps.read" };
configuration.ClientId = clientId;
configuration.PrivateKey = new JsonWebKeyConfiguration(jsonPrivateKey);
configuration.AuthorizationMode = AuthorizationMode.PrivateKey;
configuration.OktaDomain = oktaDomain;

//Initialize the OAuth API client and retrieve the access token
var oauthApi = new OAuthApi(configuration);
var tokenResponse = await oauthApi.GetBearerTokenAsync();

tokenResponse.Should().NotBeNull();
tokenResponse.AccessToken.Should().NotBeNullOrEmpty();
tokenResponse.Scope.Should().Contain("okta.apps.read");
tokenResponse.Scope.Should().Contain("okta.users.read");
}
finally
{
requestOptions = GetBasicRequestOptions();
await apiClient.DeleteAsync<JObject>($"/oauth2/v1/clients/{clientId}", requestOptions, Configuration.GetConfigurationOrDefault());
}
}

private RequestOptions GetBasicRequestOptions()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Okta.Sdk/Api/OAuthApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public async Task<ApiResponse<OAuthTokenResponse>> GetBearerTokenWithHttpInfoAsy

_dpopProofJwtGenerator.RotateKeys();
var jwtSecurityToken = _clientAssertionJwtGenerator.GenerateJwt();
var scopes = string.Join("+", Configuration.Scopes);
var scopes = string.Join(" ", Configuration.Scopes);
var accessTokenUri = "/oauth2/v1/token";

localVarRequestOptions.FormParameters.Add("grant_type", "client_credentials");
Expand Down
2 changes: 1 addition & 1 deletion src/Okta.Sdk/Okta.Sdk.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<Description>Official .NET SDK for the Okta API</Description>
<Copyright>Okta, Inc.</Copyright>
<RootNamespace>Okta.Sdk</RootNamespace>
<Version>9.0.3</Version>
<Version>9.0.4</Version>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\Okta.Sdk.xml</DocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
Expand Down

0 comments on commit dbf77e9

Please sign in to comment.