Skip to content

Latest commit

 

History

History
783 lines (529 loc) · 40 KB

MIGRATING.md

File metadata and controls

783 lines (529 loc) · 40 KB

Okta .NET management SDK migration guide

This library uses semantic versioning and follows Okta's library version policy. In short, we don't make breaking changes unless the major version changes!

Migrating from 7.x to 8.x

We have upgraded the Okta OpenAPI specifications which caused a few breaking changes due to schema changes and bug fixes in the spec.

Identity Provider breaking changes

IdentityProvider.Links now returns an instance of IdentityProviderLinks class instead of LinksSelf. The IdentityProviderLinks provides several predefined properties that were missing in 7.x; you can also access not-predefined properties via additionalProperties:

Before

var selfLinkHref = idp.Links.Self.Href;

Now

Get the Self link is the same as before:

var selfLink = idp.Links.Self.Href;

The following assertions showcase what other properties are accessible via Links, and what they look like:

idp.Links.Metadata.Href.Should().Be("https://{yourOktaDomain}/api/v1/idps/0oa1k5d68qR2954hb0g4/metadata.xml");
idp.Links.Metadata.Hints.Allow.Any(x => x == HttpMethod.GET).Should().BeTrue();
idp.Links.Metadata.Type.Should().Be("application/xml");

idp.Links.Acs.Href.Should().Be("https://{yourOktaDomain}/sso/saml2/0oa1k5d68qR2954hb0g4");
idp.Links.Acs.Hints.Allow.Any(x => x == HttpMethod.POST).Should().BeTrue();
idp.Links.Acs.Type.Should().Be("application/xml");

idp.Links.Users.Href.Should().Be("https://{yourOktaDomain}/api/v1/idps/0oa1k5d68qR2954hb0g4/users");
idp.Links.Users.Hints.Allow.Any(x => x == HttpMethod.GET).Should().BeTrue();

idp.Links.Activate.Href.Should().Be("https://{yourOktaDomain}/api/v1/idps/0oa1k5d68qR2954hb0g4/lifecycle/activate");
idp.Links.Activate.Hints.Allow.Any(x => x == HttpMethod.POST).Should().BeTrue();

idp.Links.Deactivate.Href.Should().Be("https://{yourOktaDomain}/api/v1/idps/0oa1k5d68qR2954hb0g4/lifecycle/deactivate");
idp.Links.Deactivate.Hints.Allow.Any(x => x == HttpMethod.POST).Should().BeTrue();

idp.Links.Authorize.Href.Should().Be("https://testorg.com/oauth2/v1/authorize?idp=foo");
idp.Links.Authorize.Hints.Allow.Any(x => x == HttpMethod.GET).Should().BeTrue();
idp.Links.Authorize.Templated.Should().BeTrue();

idp.Links.ClientRedirectUri.Href.Should().Be("https://testorg.com/oauth2/v1/authorize/callback");
idp.Links.ClientRedirectUri.Hints.Allow.Any(x => x == HttpMethod.POST).Should().BeTrue();

To access non-predefined links you can check the AdditionalProperties property:

var undefinedLink = (JObject)idp.Links.AdditionalProperties["undefinedLink"];
var href = undefinedLink["href"].ToString();

Note: This change fixed #700

User Schema breaking changes

UserSchemaAttribute.MinLength and UserSchemaAttribute.MaxLength are now int? instead of int.

Note: This change fixed #713.

OAuth 2.0 DPoP breaking changes

Adding support for DPoP required a few breaking changes to make the code clearer.

  • The IJwtGenerator interface was replaced by IClientAssertionJwtGenerator
  • The IOAuthTokenProvider has been updated with the following changes and the default implementation has been adapted:
    • The Task<string> GetAccessTokenAsync(bool forceRenew = false, CancellationToken cancellationToken = default); method has been replaced by Task<OAuthTokenResponse> GetAccessTokenResponseAsync(bool forceRenew = false, CancellationToken cancellationToken = default);
    • It now exposes Task AddOrUpdateAuthorizationHeader(RequestOptions requestOptions string requestUri, string httpMethod, CancellationToken cancellationToken = default); and string GetDpopProofJwt(String? nonce = null, String? httpMethod = null, String? uri = null, String? accessToken = null); methods
  • The OAuthApi constructors have been updated with the following changes:
    • The public OAuthApi(Okta.Sdk.Client.Configuration configuration = null, IJwtGenerator jwtGenerator = null) constructor has been replaced by public OAuthApi(Okta.Sdk.Client.Configuration configuration = null, IClientAssertionJwtGenerator clientAssertionJwtGenerator = null, IDpopProofJwtGenerator dpopProofJwtGenerator = null)
    • The public OAuthApi(Okta.Sdk.Client.IAsynchronousClient asyncClient, Okta.Sdk.Client.IReadableConfiguration configuration, IJwtGenerator jwtGenerator) constructor has been replaced by public OAuthApi(Okta.Sdk.Client.IAsynchronousClient asyncClient, Okta.Sdk.Client.IReadableConfiguration configuration, IClientAssertionJwtGenerator clientAssertionJwtGenerator, IDpopProofJwtGenerator dpopProofJwtGenerator)

If you have your own implementations of the updated interfaces, you will have to make the corresponding changes and implement the new exposed methods. You can check the DefaultOAuthTokenProvider and DefaultClientAssertionJwtGenerator classes for more details on how to implement the new/updated interfaces.

Policy API breaking changes

The Okta Engineering team decided to roll back the latest changes in the Policy OpenAPI specification, which were released in v7.0.6. Hence, the schema PolicyCanBeCreatedOrReplaced has been removed, and all the methods that received and/or returned an instance of PolicyCanBeCreatedOrReplaced will now accept and/or return an instance of the Policy schema. The obsolete tag has been removed from the corresponding methods.

Before in v7.0.6

System.Threading.Tasks.Task<PolicyCanBeCreatedOrReplaced> CreatePolicyAsync(  PolicyCanBeCreatedOrReplaced policy ,   bool? activate = default(bool?) , System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

System.Threading.Tasks.Task<ApiResponse<PolicyCanBeCreatedOrReplaced>> CreatePolicyWithHttpInfoAsync(  PolicyCanBeCreatedOrReplaced policy ,   bool? activate = default(bool?) , System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

System.Threading.Tasks.Task<Policy> ReplacePolicyAsync(string policyId, Policy policy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

System.Threading.Tasks.Task<ApiResponse<Policy>> ReplacePolicyWithHttpInfoAsync(string policyId, Policy policy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

Now

Note: The obsolete tag has been removed for the following methods.

System.Threading.Tasks.Task<Policy> CreatePolicyAsync(Policy policy, bool? activate = default(bool?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

System.Threading.Tasks.Task<ApiResponse<Policy>> CreatePolicyWithHttpInfoAsync(Policy policy, bool? activate = default(bool?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

System.Threading.Tasks.Task<Policy> ReplacePolicyAsync(string policyId, Policy policy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

System.Threading.Tasks.Task<ApiResponse<Policy>> ReplacePolicyWithHttpInfoAsync(string policyId, Policy policy, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));

Identity Provider Policy breaking changes

Obsolete methods and relations have been removed for the IdentityProviderPolicy schema, which no longer inherits from Policy.

Migrating from 6.x to 7.x

RestSharp upgraded to 110.2.0

We have upgraded the RestSharp dependency from 106.13.0 to 110.2.0. This caused a few breaking changes due to the removal and changes of interfaces. You can learn more about RestSharp changes here.

  • IOAuthTokenProvider.GetOAuthRetryPolicy() now returns RestResponse instead of IRestResponse. This has impacted DefaultOAuthTokenProvider.
  • RetryConfiguration.AsyncRetryPolicy now receives/returns AsyncPolicy<RestResponse> instead of AsyncPolicy<IRestResponse>
  • RetryConfiguration.GetRetryPolicy has changed its interface from
public static Polly.AsyncPolicy<IRestResponse> GetRetryPolicy(IReadableConfiguration configuration, Func<DelegateResult<IRestResponse>, TimeSpan, int, Context, Task> onRetryAsyncFunc = null)

to

  public static Polly.AsyncPolicy<RestResponse> GetRetryPolicy(IReadableConfiguration configuration, Func<DelegateResult<RestResponse>, TimeSpan, int, Context, Task> onRetryAsyncFunc = null)
  • RetryConfiguration.AddRetryHeaders has changed its interface from
public static void AddRetryHeaders (Context context, IRestRequest request)

to

public static void AddRetryHeaders (Context context, RestRequest request)
  • ApiClient.InterceptRequest partial method now receive RestRequest instead of IRestRequest
  • ApiClient.InterceptResponse partial method now receive RestResponse instead of IRestResponse

OpenAPI specification reorg and standardization

We have upgraded the Okta management OpenAPI specification to be aligned with the Okta release v2023.07.0. As part of a naming standardization performed by the API teams, a few methods have changed their name in order to use unified prefixes. For example:

  • If an operation requires a PUT request that replaces a resource with the passed values, the method will now use the Replace prefix instead of Update

  • if the method performs a partial update on a specific resource, the method will now use the PartialUpdate prefix instead of Update

  • If the operation's prefix started with Add, it now starts with Create

Some assigments and factors operations have also changed the prefix, for example:

  • CreateApplicationGroupAssignmentAsync is now AssignGroupToApplicationAsync
  • DeleteApplicationGroupAssignmentAsync is now UnassignApplicationFromGroupAsync
  • DeleteFactorAsync is now UnenrollFactorAsync

Also, some operations have been moved to their own API Client. For example, multiple operations that corresponded to an application's resource and have been part of the ApplicationsApi, are now available in their own application-resource API client:

  • ApplicationTokensApi
  • ApplicationLogosApi
  • ApplicationUsersApi
  • ApplicationGroupsApi
  • ApplicationCredentialsApi
  • ApplicationGrantsApi
  • ApplicationConnectionsApi
  • ApplicationUsersApi

Additionally, the method UserApi.DeactivateOrDeleteUserAsync has been split into two separate methods: UserApi.DeactivateUserAsync and UserApi.DeleteUserAsync.

ProfileMappingApi.UpdateProfileMappingAsync now receives an ProfileMappingRequest param instead of a Mapping param:

Before

var mapping = await _profileMappingApi.GetProfileMappingAsync(mappings.FirstOrDefault().Id);

// Add properties
if (mapping.Properties == null)
{
    mapping.Properties = new Dictionary<string, ProfileMappingProperty>();
}

mapping.Properties.Add("userType", new ProfileMappingProperty
{
    Expression = "appuser.firstName",
    PushStatus = ProfileMappingPropertyPushStatus.PUSH,
});

mapping.Properties.Add("nickName", new ProfileMappingProperty
{
    Expression = "appuser.firstName + appuser.lastName",
    PushStatus = ProfileMappingPropertyPushStatus.PUSH,
});

var updatedMapping = await _profileMappingApi.UpdateProfileMappingAsync(mapping.Id, mapping);

Now

var profileMappingRequest = new ProfileMappingRequest();
profileMappingRequest.Properties = new Dictionary<string, ProfileMappingProperty>();
profileMappingRequest.Properties.Add("userType", new ProfileMappingProperty
{
    Expression = "appuser.firstName",
    PushStatus = ProfileMappingPropertyPushStatus.PUSH,
});

profileMappingRequest.Properties.Add("nickName", new ProfileMappingProperty
{
    Expression = "appuser.firstName + appuser.lastName",
    PushStatus = ProfileMappingPropertyPushStatus.PUSH,
});


var updatedMapping = await _profileMappingApi.UpdateProfileMappingAsync(mapping.Id, profileMappingRequest);

Note: For more details about API changes, check out the latest changes on the Okta OpenAPI specification.

Enums

As a general rule, we try to avoid inline enums. However, this version of the OpenAPI specification contains a few inline enums.

Obsolete methods

The following obsolete methods have been removed:

  • UserApi
[Obsolete("This method is obsolete. Use UpdateUserAsync(string userId, User user,...) instead.")]
System.Threading.Tasks.Task<User> UpdateUserAsync(string userId, UpdateUserRequest user, bool? strict = default(bool?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
[Obsolete("This method is obsolete. Use UpdateUserWithHttpInfoAsync(string userId, User user,...) instead.")]
System.Threading.Tasks.Task<ApiResponse<User>> UpdateUserWithHttpInfoAsync(string userId, UpdateUserRequest user, bool? strict = default(bool?), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
  • RoleApi
[Obsolete("This method is obsolete. Use CreateRoleAsync(CreateIamRoleRequest,...) instead.")]
System.Threading.Tasks.Task<IamRole> CreateRoleAsync(IamRole instance, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
[Obsolete("This method is obsolete. Use CreateRoleWithHttpInfoAsync(CreateIamRoleRequest,...) instead.")]
System.Threading.Tasks.Task<ApiResponse<IamRole>> CreateRoleWithHttpInfoAsync(IamRole instance, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
[Obsolete("This method is obsolete. Use ReplaceRoleAsync(string roleIdOrLabel, UpdateIamRoleRequest,...) instead.")]
System.Threading.Tasks.Task<IamRole> ReplaceRoleAsync(string roleIdOrLabel, IamRole instance, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
[Obsolete("This method is obsolete. Use ReplaceRoleWithHttpInfoAsync(string roleIdOrLabel, UpdateIamRoleRequest,...) instead.")]
System.Threading.Tasks.Task<ApiResponse<IamRole>> ReplaceRoleWithHttpInfoAsync(string roleIdOrLabel, IamRole instance, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken));
  • ResourceSetApi
[Obsolete("This method is obsolete and will be removed in the next major release. Use CreateResourceSetAsync(CreateResourceSetRequest instance...)")]
public async System.Threading.Tasks.Task<ResourceSet> CreateResourceSetAsync(ResourceSet instance, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
[Obsolete("This method is obsolete and will be removed in the next major release. Use CreateResourceSetWithHttpInfoAsync(CreateResourceSetRequest instance...)")]
public async System.Threading.Tasks.Task<Okta.Sdk.Client.ApiResponse<ResourceSet>> CreateResourceSetWithHttpInfoAsync(ResourceSet instance, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))

Model updates

Model.IsRequired attribute is now Default to avoid JSON serialization runtime issues.

Migrating from 5.x to 6.x

In releases prior to version 6 we use an Open API v2 specification, and an Okta custom client generator to partially generate our SDK. A new version of the Open API specification (V3) has been released, and new well-known generators are now available and well received by the community. Planning the future of this SDK, we consider this a good opportunity to modernize by aligning with established standards for API client generation.

We acknowledge that migrating from v5 to v6 will require considerable effort, but we expect this change to benefit our customers in the long term.

As it's mentioned above, in previous versions we used an OpenAPI v2 specification with many custom attributes and vendor extensions that required us to implement a custom client generator that should work for all the languages Okta supports. Our process to release support for new APIs and endpoints could have been better since, most of the time we needed to add new APIs, the generator required some adjustments to support new attributes added to the spec. This process caused delays in releases that could be available for our customers sooner.

With OpenAPI v3, we saw an opportunity for improvement in several areas:

  • We can provide an API specification that follows the OpenAPI v3 standard and eliminate custom attributes and vendor extensions.
  • Given that our specification is now compliant with OASv3, we can use well-known generators used and maintained by the community. In this case, we chose openapi-generator.tech.
  • Given that we eliminated custom attributes, we can speed up our release process and let our customers access new APIs sooner. Also, everyone will have access to the OpenAPI specification and will be able to generate their own clients in other languages of their choice.

OktaClient vs API clients

In releases prior to version 6, you would instantiate a global OktaClient and access specific API clients via its properties. Now, each API has its own client and you only instantiate those clients you are interested in:

Before:

var oktaClient = new OktaClient();
var apps = await oktaClient.Applications.ListApplications().ToListAsync();

Now:

var appApiClient = new ApplicationApi();
var apps = await appApiClient.ListApplications().ToListAsync();

Note: Check out the SDK tests to see more 6.x APIs examples.

Dependency Injection in ASP.NET Core

In order to implement DI, you have to register your APIs in the Dependency Injection Container:

Before:

1- Register your OktaClient in the Startup.cs or Program.cs file.

// Startup.cs or Program.cs
// ...
// This sample uses OAuth but you can also use your API Token
builder.Services.AddScoped<IOktaClient>(_ => new OktaClient(
    new Configuration
    {
        OktaDomain = "https://myOktaDomain.com/",
        Scopes = new List<string> { "okta.users.read" },
        ClientId = "CLIENT_ID",
        AuthorizationMode = AuthorizationMode.PrivateKey,
        PrivateKey = new JsonWebKeyConfiguration("JSON_PRIVATE_KEY"),
    }));

var app = builder.Build();

2- Inject your OktaClient in your controllers or Minimal APIs

app.MapGet("/users", async (IOktaClient oktaClient) =>
    {
        return await oktaClient.Users.ListUsers().ToListAsync();
    });

Now:

1- Register your APIs in the Startup.cs or Program.cs file.

// Startup.cs or Program.cs
// ...
// This sample uses OAuth but you can also use your API Token
builder.Services.AddScoped<IUserApi>(_ => new UserApi(
    new Configuration
    {
        OktaDomain = "https://myOktaDomain.com/",
        Scopes = new HashSet<string> { "okta.users.read" },
        ClientId = "CLIENT_ID",
        AuthorizationMode = AuthorizationMode.PrivateKey,
        PrivateKey = new JsonWebKeyConfiguration("JSON_PRIVATE_KEY"),
    }));

var app = builder.Build();

2- Inject your APIs in your controllers or Minimal APIs

app.MapGet("/users", async (IUserApi api) =>
    {
        return await api.ListUsers().ToListAsync();
    });

Note: Consider registering groups of services if you need to configure DI for multiple APIs.

Configuration

Unlike previous versions, ConnectionTimeoutand RequestTimeout should now expressed in milliseconds. Also, Scopes is now a HashSet<string> to avoid duplicated entries.

Enums

StringEnums are still supported. However, format might slightly change depending on the OpenAPI specification and codegen.

Resource model removed

In previous versions, all models inherited from Resource to facilitate the JSON serialization process and manipulate raw data. In series 6.x, this is no longer the case; The SDK now uses the JSON SubTypes dependency.

Manipulate Custom Attributes

Models that support dynamic properties now expose the AdditionalProperties property:

Before:

user.Profile["homeworld"] = "Tattooine";

Now:

user.Profile.AdditionalProperties = new Dictionary<string, object>();
user.Profile.AdditionalProperties["homeworld"] = "Planet Earth";

Get data from _links

In previous versions, you had to manipulate raw data via the Resource convenience methods to access Links. Now, Links are exposed are standard properties:

Before:

var accessPolicyHref = createdApp.GetProperty<Resource>("_links")?
          .GetProperty<Resource>("accessPolicy")?
          .GetProperty<string>("href");

Now:

var accessPolicyHref = createdApp.Links.AccessPolicy.Href;

Cast polymorphic models

In previous versions, the OktaClient provided a generic Get method where devs could provide a specifc type to cast a model. Now, you have to cast a model after a Get method using as:

Before:

var retrievedApp = await client.Applications.GetApplicationAsync<IBookmarkApplication>(createdApp.Id);

Now:

var retrievedApp = await _applicationApi.GetApplicationAsync(createdApp.Id) as BookmarkApplication;

Handling errors

The now SDK throws an ApiException every time the server responds with an invalid status code, or there is an internal error.

Before:

try
{
    // ...
    var retrievedApp = await client.Applications.GetApplicationAsync<IBookmarkApplication>("unknownId");
}    
catch (OktaApiException apiException)
{
    var message = apiException.Message; //"Not found: Resource not found: foo (AppInstance) (404, E0000007)"
    var statusCode = apiException.StatusCode; //404
    var errorSummary = apiException.ErrorSummary; //"Not found: Resource not found: foo (AppInstance)"
    var errorId = apiException.ErrorId; //"E0000007"
    var errorCode = apiException.ErrorCode; //"E0000007"
}

Now:

try
{
    //...
    var retrievedApp = await _applicationApi.GetApplicationAsync("unknownId") as BookmarkApplication;
}
catch (ApiException apiException)
{
    var message = apiException.Message; //Error calling GetApplication: {"errorCode":"E0000007","errorSummary":"Not found: Resource not found: foo (AppInstance)","errorLink":"E0000007","errorId":"oaeWzp-a2A0TCOm7D0FtnHcbg","errorCauses":[]}
    var statusCode = apiException.ErrorCode; //404
    var errorContent = apiException.ErrorContent; //{"errorCode":"E0000007","errorSummary":"Not found: Resource not found: foo (AppInstance)","errorLink":"E0000007","errorId":"oaeWzp-a2A0TCOm7D0FtnHcbg","errorCauses":[]}
    
    //ErrorId and ErrorCode should be parsed from ErrorContent at the moment, but convenience methods will be added soon. OKTA-555564
}

Features parity

The following features have been ported to 6.x:

  • Iniline configuration, configuration via environment variables, appsettings.json or YAML files
  • Manual pagination for collections
  • Default retry strategy for 429 HTTP responses and ability to provide your own strategy
  • Web proxy
  • OAuth for Okta

Dependencies

We now use RestSharp as our internal REST API client library unlike previous versions which were using HttpClient. For more details about other dependencies, please check out the Dependencies section here.

New APIs

In order to provide better structuring, some endpoints have been moved from an existing client/API to their own API client:

Before:

var oktaClient = new OktaClient();

await oktaClient.Groups.AssignRoleAsync(groupId, new AssignRoleRequest
                {
                    Type = RoleType.UserAdmin,
                });

var roles = await oktaClient.Groups.ListGroupAssignedRoles(createdGroup.Id).ToListAsync();

/// ...

await oktaClient.Groups.AddGroupTargetToGroupAdministratorRoleForGroupAsync(createdGroup1.Id, role.Id, createdGroup2.Id);

var groups = await oktaClient.Groups.ListGroupTargetsForGroupRole(createdGroup1.Id, role.Id).ToListAsync();

Now:

var _roleAssignmentApi = new RoleAssignmentApi();

var role1 = await _roleAssignmentApi.AssignRoleToGroupAsync(createdGroup.Id, new AssignRoleRequest
                {
                    Type = "SUPER_ADMIN"
                });

var roles = await _roleAssignmentApi.ListGroupAssignedRoles(createdGroup.Id).ToListAsync();

/// ...

var _roleTargetApi = new RoleTargetApi();

await _roleTargetApi.AddGroupTargetToGroupAdministratorRoleForGroupAsync(group1.Id, role1.Id, group2.Id);

var groupTargetList = await _roleTargetApi.ListGroupTargetsForGroupRole(createdGroup1.Id, role1.Id).ToListAsync();

For more details about other APIs, please check out here.

Rate Limit

The SDK uses Polly to implement the retry strategy when rate limit has been exceeded. The default retry strategy behavior and the way you configure it remains the same. However, if you want to provide your own retry logic you have to use Polly. Check out the README for more details.

Migrating from 4.x to 5.x

In previous versions, null resource properties would result in a resource object with all its properties set to null. Now, null resource properties will result in null property value.

Before:

{                                                 deserializedResource.Prop1.Should().Be("Hello World!");          
    prop1 : "Hello World!",         =>            deserializedResource.NestedObject.Should().NotBeNull();
    nestedObject: null                            deserializedResource.NestedObject.Prop1.Should().BeNull();
}

Now:

{                                                 deserializedResource.Prop1.Should().Be("Hello World!");          
    prop1 : "Hello World!",         =>            deserializedResource.NestedObject.Should().BeNull();
    nestedObject: null                            
}

Since this is a breaking change in the default behavior, the major version of Okta.Sdk was incremented to 5; the latest version is now 5.0.0.

If you were relying on this behavior, make sure to update your code and verify the resource is not null before accessing its properties.

Migrating from 3.x to 4.x

The 3.x series of this library introduced a new client for Authorization Servers. This client had an issue when trying to retrieve policy rules for given Authorization Server Policy. In order to fix this issue, new policy models were created to represent both policies and policy rules for Authorization Servers.

Because this was a breaking change, Okta.Sdk was published with version numbers starting from 4.0.0.

All Authorization Server methods that manipulate Policies and/or Policy Rules have changed:

AuthorizationServer

Below APIs has undergone a signature change.

  • public ICollectionClient<IPolicy> ListPolicies() changed to public ICollectionClient<IAuthorizationServerPolicy> ListPolicies()

Note that the method returns now an IAuthorizationServerPolicy.

  • public Task<IPolicy> CreatePolicyAsync(IPolicy policy, CancellationToken cancellationToken = default(CancellationToken)) changed to public Task<IAuthorizationServerPolicy> CreatePolicyAsync(IAuthorizationServerPolicy policy, CancellationToken cancellationToken = default(CancellationToken))

Note that the method expects and returns now an IAuthorizationServerPolicy.

  • public Task<IPolicy> GetPolicyAsync(string policyId, CancellationToken cancellationToken = default(CancellationToken)) changed to public Task<IAuthorizationServerPolicy> GetPolicyAsync(string policyId, CancellationToken cancellationToken = default(CancellationToken))

Note that the method returns now an IAuthorizationServerPolicy.

AuthorizationServersClient

Below APIs has undergone a signature change.

  • public ICollectionClient<IPolicy> ListAuthorizationServerPolicies(string authServerId) changed to public ICollectionClient<IAuthorizationServerPolicy> ListAuthorizationServerPolicies(string authServerId)

Note that the method returns now an ICollectionClient of IAuthorizationServerPolicy.

  • public async Task<IPolicy> CreateAuthorizationServerPolicyAsync(IPolicy policy, string authServerId, CancellationToken cancellationToken = default(CancellationToken)) change to public async Task<IAuthorizationServerPolicy> CreateAuthorizationServerPolicyAsync(IAuthorizationServerPolicy policy, string authServerId, CancellationToken cancellationToken = default(CancellationToken))

Note that the method expects and returns now an IAuthorizationServerPolicy.

  • public async Task<IPolicy> GetAuthorizationServerPolicyAsync(string authServerId, string policyId, CancellationToken cancellationToken = default(CancellationToken)) changed to public async Task<IAuthorizationServerPolicy> GetAuthorizationServerPolicyAsync(string authServerId, string policyId, CancellationToken cancellationToken = default(CancellationToken))

Note that the method returns now an IAuthorizationServerPolicy.

  • public async Task<IPolicy> UpdateAuthorizationServerPolicyAsync(IPolicy policy, string authServerId, string policyId, CancellationToken cancellationToken = default(CancellationToken)) changed to public async Task<IAuthorizationServerPolicy> UpdateAuthorizationServerPolicyAsync(IAuthorizationServerPolicy policy, string authServerId, string policyId, CancellationToken cancellationToken = default(CancellationToken))

Note that the method expects and returns now an IAuthorizationServerPolicy.

You now can get policy rules given an Authorization Server Policy:

var authorizationServerPolicy = await authorizationServer.GetPolicyAsync(policy.Id);
var authorizationServerPolicyRules = await authorizationServerPolicy.ListPolicyRules(authorizationServer.Id).ToListAsync();

Migrating from 2.0.0 to 3.x

Version 3.0.0 of this library introduces a number of breaking changes from previous versions; in addition to new classes some class definitions are no longer backward compatible due to method renames and signature changes, see Breaking Changes.

Breaking Changes

The following is a list of changes that break backward compatibility in version 3.0.0.

Okta.Sdk.OktaClient

  • CreatedScoped(Okta.Sdk.RequestContext requestContext)
    — Renamed CreateScoped(Okta.Sdk.RequestContext requestContext)

Okta.Sdk.GroupsClient

  • ListGroups(string q, string filter, string after, int limit, string expand)
    — Signature changed ListGroups(string q, string filter, string after, int limit)
  • ListRules(int limit, string after, string expand)
    — Renamed with new signature ListGroupRules(int limit, string after, string search, string expand)
  • CreateRuleAsync(Okta.Sdk.IGroupRule groupRule, CancellationToken cancellationToken)
    — Renamed CreateGroupRuleAsync(Okta.Sdk.IGroupRule groupRule, CancellationToken cancellationToken)
  • DeleteRuleAsync(string ruleId, bool removeUsers, CancellationToken cancellationToken)
    — Renamed with new signature DeleteGroupRuleAsync(string ruleId, CancellationToken cancellationToken)
  • GetRuleAsync(string ruleId, string expand, CancellationToken cancellationToken)
    — Renamed GetGroupRuleAsync(string ruleId, string expand, CancellationToken cancellationToken)
  • UpdateRuleAsync(Okta.Sdk.IGroupRule groupRule, string ruleId, CancellationToken cancellationToken)
    — Renamed UpdateGroupRuleAsync(Okta.Sdk.IGroupRule groupRule, string ruleId, CancellationToken cancellationToken)
  • ActivateRuleAsync(string ruleId, CancellationToken cancellationToken)
    — Renamed ActivateGroupRuleAsync(string ruleId, CancellationToken cancellationToken)
  • DeactivateRuleAsync(string ruleId, CancellationToken cancellationToken)
    — Renamed DeactivateGroupRuleAsync(string ruleId, CancellationToken cancellationToken)
  • GetGroupAsync(string groupId, string expand, CancellationToken cancellationToken)
    — Signature changed GetGroupAsync(string groupId, CancellationToken cancellationToken)
  • ListGroupUsers(string groupId, string after, int limit, string managedBy)
    — Signature changed ListGroupUsers(string groupId, string after, int limit)
  • RemoveGroupUserAsync(string groupId, string userId, CancellationToken cancellationToken)
    — Renamed RemoveUserFromGroupAsync(string groupId, string userId, CancellationToken cancellationToken)

Okta.Sdk.PoliciesClient

  • ListPolicies(string type, string status, string after, int limit, string expand)
    — Signature changed ListPolicies(string type, string status, string expand)
  • AddPolicyRuleAsync(Okta.Sdk.IPolicyRule policyRule, string policyId, bool activate, CancellationToken cancellationToken)
    — Signature changed AddPolicyRuleAsync(Okta.Sdk.IPolicyRule policyRule, string policyId, CancellationToken cancellationToken)

Okta.Sdk.UserFactorsClient

  • AddFactorAsync(Okta.Sdk.IFactor factor, string userId, bool updatePhone, string templateId, int tokenLifetimeSeconds, bool activate, CancellationToken cancellationToken)
    — Renamed with new signature EnrollFactorAsync(Okta.Sdk.IUserFactor body, string userId, bool updatePhone, string templateId, int tokenLifetimeSeconds, bool activate, CancellationToken ca ncellationToken)
  • ActivateFactorAsync(Okta.Sdk.IVerifyFactorRequest verifyFactorRequest, string userId, string factorId, CancellationToken cancellationToken)
    — Renamed with new signature ActivateFactorAsync(Okta.Sdk.IActivateFactorRequest body, string userId, string factorId, CancellationToken cancellationToken)

Okta.Sdk.UsersClient

  • ListUsers(string q, string after, int limit, string filter, string format, string search, string expand)
    — Signature changed ListUsers(string q, string after, int limit, string filter, string search, string sortBy, string sortOrder)
  • CreateUserAsync(Okta.Sdk.IUser user, bool activate, bool provider, Okta.Sdk.UserNextLogin nextLogin, CancellationToken cancellationToken)
    — Signature changed CreateUserAsync(Okta.Sdk.ICreateUserRequest body, bool activate, bool provider, Okta.Sdk.UserNextLogin nextLogin, CancellationToken cancellationToken)
  • ListAppLinks(string userId, bool showAll)
    — Signature changed ListAppLinks(string userId)
  • ListUserGroups(string userId, string after, int limit)
    — Signature changed ListUserGroups(string userId)
  • ExpirePasswordAsync(string userId, bool tempPassword, CancellationToken cancellationToken)
    — Signature changed ExpirePasswordAsync(string userId, CancellationToken cancellationToken)
  • ResetAllFactorsAsync(string userId, CancellationToken cancellationToken)
    — Renamed ResetFactorsAsync(string userId, CancellationToken cancellationToken)
  • ResetPasswordAsync(string userId, Okta.Sdk.AuthenticationProviderType provider, bool sendEmail, CancellationToken cancellationToken)
    — Removed; instead use any of the following:
    • ForgotPasswordGenerateOneTimeTokenAsync(string userId, bool sendEmail, CancellationToken cancellationToken)
    • ForgotPasswordSetNewPasswordAsync(Okta.Sdk.IUserCredentials user, string userId, bool sendEmail, CancellationToken cancellationToken)
    • ExpirePasswordAsync(string userId, CancellationToken cancellationToken)
    • ExpirePasswordAndGetTemporaryPasswordAsync(string userId, CancellationToken cancellationToken)
  • ListAssignedRoles(string userId, string expand)
    — Renamed ListAssignedRolesForUser(string userId, string expand)
  • EndAllUserSessionsAsync(string userId, bool oauthTokens, CancellationToken cancellationToken)
    — Renamed ClearUserSessionsAsync(string userId, bool oauthTokens, CancellationToken cancellationToken)

New Okta Clients

The following is a list of context specific clients that are new in version 3.0.0. Instances of each are available as properties of an OktaClient instance where the name of the property is the name of the type with the "Client" suffix removed.

Migrating from 0.3.3 to 1.x

The previous version of this library, Okta.Core.Client, has been rewritten from the ground up as Okta.Sdk (this project). This was done to improve stability and to add support for .NET Core alongside .NET Framework.

Because this was a breaking change, Okta.Sdk was published with version numbers starting from 1.0. The last published version of Okta.Core.Client is 0.3.3.

New configuration model

This library now supports a flexible configuration model that allows you to provide configuration in code, via a JSON or YAML file, or via environment variables.

The simplest way to construct a client is via code:

var client = new OktaClient(new OktaClientConfiguration
{
    OrgUrl = "https://{{yourOktaDomain}}",
    Token = "{{yourApiToken}}"
});

New method organization

In version 0.3.3, you had to create a new UsersClient() or call client.GetUsersClient() to get access to methods that operated on a User (for example). This has now been simplified to client.Users:

var vader = await client.Users.CreateUserAsync(...);

The Users object acts as a collection, so you can also do:

var allUsers = await client.Users.ToArray();

The readme in this repository contains a number of usage examples.

Async by default

In version 1.0 and above, every method that makes a network call is Task-returning and awaitable. Use await when calling these methods, and avoid using .Result or .Wait() unless absolutely necessary.

Authentication API support moved

In version 0.3.3, the AuthClient class provided the ability to call the Authentication API to log a user in with a username and password, or perform other tasks like enrolling and challenging factors during authentication. The object and security model of the Authentication API compared the rest of the management APIs (Users, Factors, Groups, etc.) is different enough that it made sense to split it into two libraries.

Starting with version 1.0, Authentication has been broken out into a separate library, the Okta .NET Authentication SDK.

Many applications can use our ASP.NET and ASP.NET Core middleware to log users in without needing to call the Authentication API directly, and we recommend only using the Authentication SDK in complex scenarios that can't be handled with the middleware or widget.

Getting help

If you have questions about this library or about the Okta APIs, post a question on our Developer Forum.

If you find a bug or have a feature request for this library specifically, post an issue here on GitHub.