Skip to content

Commit

Permalink
Support custom Okta authorization servers
Browse files Browse the repository at this point in the history
Add built-in support for using a custom Okta Authorization Server.
  • Loading branch information
martincostello committed Jan 6, 2022
1 parent 1dcd240 commit b5e3a43
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/okta.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ services.AddAuthentication(options => /* Auth configuration */)

| Property Name | Property Type | Description | Default Value |
|:--|:--|:--|:--|
| `AuthorizationServer` | `string` | The Okta custom authorization server to use for authentication. | `default` |
| `Domain` | `string?` | The Okta domain (_Org URL_) to use for authentication. This can be found on the `/dev/console` page of the Okta admin portal for your account. | `null` |

## Optional Settings
Expand Down
28 changes: 25 additions & 3 deletions src/AspNet.Security.OAuth.Okta/OktaAuthenticationDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* for more information concerning the license and the contributors participating to this project.
*/

using System.Globalization;

namespace AspNet.Security.OAuth.Okta;

/// <summary>
Expand All @@ -26,23 +28,43 @@ public static class OktaAuthenticationDefaults
/// </summary>
public static readonly string Issuer = "Okta";

/// <summary>
/// The name of the default Okta custom Authorization Server.
/// </summary>
public static readonly string DefaultAuthorizationServer = "default";

/// <summary>
/// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
/// </summary>
public static readonly string CallbackPath = "/signin-okta";

/// <summary>
/// Default path format to use for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
/// </summary>
public static readonly string AuthorizationEndpointPathFormat = "/oauth2/{0}/v1/authorize";

/// <summary>
/// Default path format to use for <see cref="OAuthOptions.TokenEndpoint"/>.
/// </summary>
public static readonly string TokenEndpointPathFormat = "/oauth2/{0}/v1/token";

/// <summary>
/// Default path format to use for <see cref="OAuthOptions.UserInformationEndpoint"/>.
/// </summary>
public static readonly string UserInformationEndpointPathFormat = "/oauth2/{0}/v1/userinfo";

/// <summary>
/// Default path to use for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
/// </summary>
public static readonly string AuthorizationEndpointPath = "/oauth2/default/v1/authorize";
public static readonly string AuthorizationEndpointPath = string.Format(CultureInfo.InvariantCulture, AuthorizationEndpointPathFormat, DefaultAuthorizationServer);

/// <summary>
/// Default path to use for <see cref="OAuthOptions.TokenEndpoint"/>.
/// </summary>
public static readonly string TokenEndpointPath = "/oauth2/default/v1/token";
public static readonly string TokenEndpointPath = string.Format(CultureInfo.InvariantCulture, TokenEndpointPathFormat, DefaultAuthorizationServer);

/// <summary>
/// Default path to use for <see cref="OAuthOptions.UserInformationEndpoint"/>.
/// </summary>
public static readonly string UserInformationEndpointPath = "/oauth2/default/v1/userinfo";
public static readonly string UserInformationEndpointPath = string.Format(CultureInfo.InvariantCulture, UserInformationEndpointPathFormat, DefaultAuthorizationServer);
}
8 changes: 8 additions & 0 deletions src/AspNet.Security.OAuth.Okta/OktaAuthenticationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ public OktaAuthenticationOptions()
ClaimActions.MapJsonKey(ClaimTypes.Surname, "family_name");
}

/// <summary>
/// Gets or sets the Okta custom authorization server to use for authentication.
/// </summary>
/// <remarks>
/// The default value is <c>default</c>.
/// </remarks>
public string AuthorizationServer { get; set; } = OktaAuthenticationDefaults.DefaultAuthorizationServer;

/// <summary>
/// Gets or sets the Okta domain (Org URL) to use for authentication.
/// </summary>
Expand Down
16 changes: 12 additions & 4 deletions src/AspNet.Security.OAuth.Okta/OktaPostConfigureOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* for more information concerning the license and the contributors participating to this project.
*/

using System.Globalization;
using Microsoft.Extensions.Options;

namespace AspNet.Security.OAuth.Okta;
Expand All @@ -23,13 +24,20 @@ public void PostConfigure(
throw new ArgumentException("No Okta domain configured.", nameof(options));
}

options.AuthorizationEndpoint = CreateUrl(options.Domain, OktaAuthenticationDefaults.AuthorizationEndpointPath);
options.TokenEndpoint = CreateUrl(options.Domain, OktaAuthenticationDefaults.TokenEndpointPath);
options.UserInformationEndpoint = CreateUrl(options.Domain, OktaAuthenticationDefaults.UserInformationEndpointPath);
if (string.IsNullOrWhiteSpace(options.AuthorizationServer))
{
throw new ArgumentException("No Okta authorization server configured.", nameof(options));
}

options.AuthorizationEndpoint = CreateUrl(options.Domain, OktaAuthenticationDefaults.AuthorizationEndpointPathFormat, options.AuthorizationServer);
options.TokenEndpoint = CreateUrl(options.Domain, OktaAuthenticationDefaults.TokenEndpointPathFormat, options.AuthorizationServer);
options.UserInformationEndpoint = CreateUrl(options.Domain, OktaAuthenticationDefaults.UserInformationEndpointPathFormat, options.AuthorizationServer);
}

private static string CreateUrl(string domain, string path)
private static string CreateUrl(string domain, string pathFormat, params object[] args)
{
var path = string.Format(CultureInfo.InvariantCulture, pathFormat, args);

// Enforce use of HTTPS
var builder = new UriBuilder(domain)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ public static void PostConfigure_Configures_Valid_Endpoints(string domain)
Uri.TryCreate(options.UserInformationEndpoint, UriKind.Absolute, out _).ShouldBeTrue();
}

[Theory]
[InlineData("okta.local")]
[InlineData("http://okta.local")]
[InlineData("http://okta.local/")]
[InlineData("https://okta.local")]
[InlineData("https://okta.local/")]
public static void PostConfigure_Configures_Valid_Endpoints_With_Custom_Authorization_Server(string domain)
{
// Arrange
string name = "Okta";
var target = new OktaPostConfigureOptions();

var options = new OktaAuthenticationOptions()
{
AuthorizationServer = "custom",
Domain = domain,
};

// Act
target.PostConfigure(name, options);

// Assert
options.AuthorizationEndpoint.ShouldStartWith("https://okta.local/oauth2/custom/v1/");
Uri.TryCreate(options.AuthorizationEndpoint, UriKind.Absolute, out _).ShouldBeTrue();

options.TokenEndpoint.ShouldStartWith("https://okta.local/oauth2/custom/v1/");
Uri.TryCreate(options.TokenEndpoint, UriKind.Absolute, out _).ShouldBeTrue();

options.UserInformationEndpoint.ShouldStartWith("https://okta.local/oauth2/custom/v1/");
Uri.TryCreate(options.UserInformationEndpoint, UriKind.Absolute, out _).ShouldBeTrue();
}

[Theory]
[InlineData(null)]
[InlineData("")]
Expand All @@ -57,4 +89,24 @@ public static void PostConfigure_Throws_If_Domain_Is_Invalid(string value)
// Act and Assert
Assert.Throws<ArgumentException>("options", () => target.PostConfigure(name, options));
}

[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public static void PostConfigure_Throws_If_AuthorizationServer_Is_Invalid(string value)
{
// Arrange
string name = "Okta";
var target = new OktaPostConfigureOptions();

var options = new OktaAuthenticationOptions()
{
AuthorizationServer = value,
Domain = "okta.local",
};

// Act and Assert
Assert.Throws<ArgumentException>("options", () => target.PostConfigure(name, options));
}
}
26 changes: 26 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Okta/OktaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,30 @@ public async Task Can_Sign_In_Using_Okta(string claimType, string claimValue)
// Assert
AssertClaim(claims, claimType, claimValue);
}

[Theory]
[InlineData(ClaimTypes.Email, "jane.doe@example.com")]
[InlineData(ClaimTypes.GivenName, "Jane")]
[InlineData(ClaimTypes.Name, "Jane Doe")]
[InlineData(ClaimTypes.NameIdentifier, "00uid4BxXw6I6TV4m0g4")]
[InlineData(ClaimTypes.Surname, "Doe")]
public async Task Can_Sign_In_Using_Okta_With_Custom_Authorization_Server(string claimType, string claimValue)
{
// Arrange
static void ConfigureServices(IServiceCollection services)
{
services.Configure<OktaAuthenticationOptions>("Okta", (options) =>
{
options.AuthorizationServer = "custom";
});
}

using var server = CreateTestServer(ConfigureServices);

// Act
var claims = await AuthenticateUserAsync(server);

// Assert
AssertClaim(claims, claimType, claimValue);
}
}
41 changes: 41 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Okta/bundle.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,47 @@
},
"phone_number": "+1 (425) 555-1212"
}
},
{
"comment": "https://developer.okta.com/docs/reference/api/oidc/#response-example-success-2",
"uri": "https://okta.local/oauth2/custom/v1/token",
"method": "POST",
"contentFormat": "json",
"contentJson": {
"access_token": "secret-access-token",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid email",
"refresh_token": "secret-refresh-token",
"id_token": "secret-id-token"
}
},
{
"comment": "https://developer.okta.com/docs/reference/api/oidc/#userinfo",
"uri": "https://okta.local/oauth2/custom/v1/userinfo",
"contentFormat": "json",
"contentJson": {
"sub": "00uid4BxXw6I6TV4m0g4",
"name": "Jane Doe",
"nickname": "Jan",
"given_name": "Jane",
"middle_name": "Jenny",
"family_name": "Doe",
"profile": "https://example.com/jane.doe",
"zoneinfo": "America/Los_Angeles",
"locale": "en-US",
"updated_at": 1311280970,
"email": "jane.doe@example.com",
"email_verified": true,
"address": {
"street_address": "123 Hollywood Blvd.",
"locality": "Los Angeles",
"region": "CA",
"postal_code": "90210",
"country": "US"
},
"phone_number": "+1 (425) 555-1212"
}
}
]
}

0 comments on commit b5e3a43

Please sign in to comment.