diff --git a/Okta.AspNet.Test/OpenIdConnectAuthenticationOptionsBuilderShould.cs b/Okta.AspNet.Test/OpenIdConnectAuthenticationOptionsBuilderShould.cs new file mode 100644 index 0000000..0211309 --- /dev/null +++ b/Okta.AspNet.Test/OpenIdConnectAuthenticationOptionsBuilderShould.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) 2018-present Okta, Inc. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using FluentAssertions; +using Microsoft.Owin.Security.OpenIdConnect; +using Okta.AspNet.Abstractions; +using Xunit; + +namespace Okta.AspNet.Test +{ + public class OpenIdConnectAuthenticationOptionsBuilderShould + { + [Fact] + public void BuildOpenIdConnectAuthenticationOptionsCorrectly() + { + var oktaMvcOptions = new OktaMvcOptions() + { + PostLogoutRedirectUri = "http://postlogout.com", + OktaDomain = "http://myoktadomain.com", + ClientId = "foo", + ClientSecret = "bar", + RedirectUri = "/redirectUri", + Scope = new List { "openid", "profile", "email" }, + }; + + var notifications = new OpenIdConnectAuthenticationNotifications + { + RedirectToIdentityProvider = null, + }; + + var oidcOptions = OpenIdConnectAuthenticationOptionsBuilder.BuildOpenIdConnectAuthenticationOptions( + oktaMvcOptions, + notifications); + + oidcOptions.ClientId.Should().Be(oktaMvcOptions.ClientId); + oidcOptions.ClientSecret.Should().Be(oktaMvcOptions.ClientSecret); + oidcOptions.PostLogoutRedirectUri.Should().Be(oktaMvcOptions.PostLogoutRedirectUri); + + var issuer = UrlHelper.CreateIssuerUrl(oktaMvcOptions.OktaDomain, oktaMvcOptions.AuthorizationServerId); + oidcOptions.Authority.Should().Be(issuer); + oidcOptions.RedirectUri.Should().Be(oktaMvcOptions.RedirectUri); + oidcOptions.Scope.Should().Be(string.Join(" ", oktaMvcOptions.Scope)); + } + } +} diff --git a/Okta.AspNet/OktaMiddlewareExtensions.cs b/Okta.AspNet/OktaMiddlewareExtensions.cs index 5307713..75c8b82 100644 --- a/Okta.AspNet/OktaMiddlewareExtensions.cs +++ b/Okta.AspNet/OktaMiddlewareExtensions.cs @@ -81,45 +81,15 @@ private static void AddJwtBearerAuthentication(IAppBuilder app, OktaWebApiOption private static void AddOpenIdConnectAuthentication(IAppBuilder app, OktaMvcOptions options) { - var issuer = UrlHelper.CreateIssuerUrl(options.OktaDomain, options.AuthorizationServerId); - var httpClient = new HttpClient(new UserAgentHandler("okta-aspnet", typeof(OktaMiddlewareExtensions).Assembly.GetName().Version)); - - var configurationManager = new ConfigurationManager( - issuer + "/.well-known/openid-configuration", - new OpenIdConnectConfigurationRetriever(), - new HttpDocumentRetriever(httpClient)); - - var tokenValidationParameters = new DefaultTokenValidationParameters(options, issuer) - { - NameClaimType = "name", - ValidAudience = options.ClientId, - }; - - var tokenExchanger = new TokenExchanger(options, issuer, configurationManager); - // Stop the default behavior of remapping JWT claim names to legacy MS/SOAP claim names JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - var definedScopes = options.Scope?.ToArray() ?? OktaDefaults.Scope; - var scopeString = string.Join(" ", definedScopes); - - app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions + var notifications = new OpenIdConnectAuthenticationNotifications { - ClientId = options.ClientId, - ClientSecret = options.ClientSecret, - Authority = issuer, - RedirectUri = options.RedirectUri, - ResponseType = OpenIdConnectResponseType.CodeIdToken, - Scope = scopeString, - PostLogoutRedirectUri = options.PostLogoutRedirectUri, - TokenValidationParameters = tokenValidationParameters, - SecurityTokenValidator = new StrictSecurityTokenValidator(), - Notifications = new OpenIdConnectAuthenticationNotifications - { - AuthorizationCodeReceived = tokenExchanger.ExchangeCodeForTokenAsync, - RedirectToIdentityProvider = BeforeRedirectToIdentityProviderAsync, - }, - }); + RedirectToIdentityProvider = BeforeRedirectToIdentityProviderAsync, + }; + + app.UseOpenIdConnectAuthentication(OpenIdConnectAuthenticationOptionsBuilder.BuildOpenIdConnectAuthenticationOptions(options, notifications)); } private static Task BeforeRedirectToIdentityProviderAsync(RedirectToIdentityProviderNotification n) diff --git a/Okta.AspNet/OpenIdConnectAuthenticationOptionsBuilder.cs b/Okta.AspNet/OpenIdConnectAuthenticationOptionsBuilder.cs new file mode 100644 index 0000000..3ee611e --- /dev/null +++ b/Okta.AspNet/OpenIdConnectAuthenticationOptionsBuilder.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) 2018-present Okta, Inc. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +// + +using System.Linq; +using System.Net.Http; +using Microsoft.IdentityModel.Protocols; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.Owin.Security.OpenIdConnect; +using Okta.AspNet.Abstractions; + +namespace Okta.AspNet +{ + public class OpenIdConnectAuthenticationOptionsBuilder + { + /// + /// Creates a new instance of OpenIdConnectAuthenticationOptions. + /// + /// The options. + /// The OpenIdConnectAuthenticationNotifications notifications. + /// A new instance of OpenIdConnectAuthenticationOptions. + public static OpenIdConnectAuthenticationOptions BuildOpenIdConnectAuthenticationOptions(OktaMvcOptions oktaMvcOptions, OpenIdConnectAuthenticationNotifications notifications) + { + var issuer = UrlHelper.CreateIssuerUrl(oktaMvcOptions.OktaDomain, oktaMvcOptions.AuthorizationServerId); + var httpClient = new HttpClient(new UserAgentHandler("okta-aspnet", typeof(OktaMiddlewareExtensions).Assembly.GetName().Version)); + + var configurationManager = new ConfigurationManager( + issuer + "/.well-known/openid-configuration", + new OpenIdConnectConfigurationRetriever(), + new HttpDocumentRetriever(httpClient)); + + var tokenValidationParameters = new DefaultTokenValidationParameters(oktaMvcOptions, issuer) + { + NameClaimType = "name", + ValidAudience = oktaMvcOptions.ClientId, + }; + + var tokenExchanger = new TokenExchanger(oktaMvcOptions, issuer, configurationManager); + var definedScopes = oktaMvcOptions.Scope?.ToArray() ?? OktaDefaults.Scope; + var scopeString = string.Join(" ", definedScopes); + + return new OpenIdConnectAuthenticationOptions + { + ClientId = oktaMvcOptions.ClientId, + ClientSecret = oktaMvcOptions.ClientSecret, + Authority = issuer, + RedirectUri = oktaMvcOptions.RedirectUri, + ResponseType = OpenIdConnectResponseType.CodeIdToken, + Scope = scopeString, + PostLogoutRedirectUri = oktaMvcOptions.PostLogoutRedirectUri, + TokenValidationParameters = tokenValidationParameters, + SecurityTokenValidator = new StrictSecurityTokenValidator(), + Notifications = new OpenIdConnectAuthenticationNotifications + { + AuthorizationCodeReceived = tokenExchanger.ExchangeCodeForTokenAsync, + RedirectToIdentityProvider = notifications.RedirectToIdentityProvider, + }, + }; + } + } +} diff --git a/Okta.AspNetCore.Test/OpenIdConnectOptionsHelperShould.cs b/Okta.AspNetCore.Test/OpenIdConnectOptionsHelperShould.cs new file mode 100644 index 0000000..c31b526 --- /dev/null +++ b/Okta.AspNetCore.Test/OpenIdConnectOptionsHelperShould.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) 2018-present Okta, Inc. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +// + +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Okta.AspNet.Abstractions; +using Xunit; + +namespace Okta.AspNetCore.Test +{ + public class OpenIdConnectOptionsHelperShould + { + [Fact] + public void SetOpenIdConnectsOptionsCorrectly() + { + var oktaMvcOptions = new OktaMvcOptions + { + PostLogoutRedirectUri = "http://foo.postlogout.com", + AuthorizationServerId = "bar", + ClientId = "foo", + ClientSecret = "baz", + OktaDomain = "http://myoktadomain.com", + GetClaimsFromUserInfoEndpoint = true, + CallbackPath = "/somecallbackpath", + Scope = new List { "openid", "profile", "email" }, + }; + + var events = new OpenIdConnectEvents() { OnRedirectToIdentityProvider = null }; + + var oidcOptions = new OpenIdConnectOptions(); + + OpenIdConnectOptionsHelper.ConfigureOpenIdConnectOptions(oktaMvcOptions, events, oidcOptions); + + oidcOptions.ClientId.Should().Be(oktaMvcOptions.ClientId); + oidcOptions.ClientSecret.Should().Be(oktaMvcOptions.ClientSecret); + oidcOptions.SignedOutRedirectUri.Should().Be(oktaMvcOptions.PostLogoutRedirectUri); + oidcOptions.GetClaimsFromUserInfoEndpoint.Should().Be(oktaMvcOptions.GetClaimsFromUserInfoEndpoint); + oidcOptions.CallbackPath.Value.Should().Be(oktaMvcOptions.CallbackPath); + + var issuer = UrlHelper.CreateIssuerUrl(oktaMvcOptions.OktaDomain, oktaMvcOptions.AuthorizationServerId); + oidcOptions.Authority.Should().Be(issuer); + + oidcOptions.Scope.ToList().Should().BeEquivalentTo(oktaMvcOptions.Scope); + oidcOptions.CallbackPath.Value.Should().Be(oktaMvcOptions.CallbackPath); + oidcOptions.Events.OnRedirectToIdentityProvider.Should().BeNull(); + } + } +} diff --git a/Okta.AspNetCore/OktaAuthenticationOptionsExtensions.cs b/Okta.AspNetCore/OktaAuthenticationOptionsExtensions.cs index db28e16..7bbf495 100644 --- a/Okta.AspNetCore/OktaAuthenticationOptionsExtensions.cs +++ b/Okta.AspNetCore/OktaAuthenticationOptionsExtensions.cs @@ -5,13 +5,10 @@ using System; using System.IdentityModel.Tokens.Jwt; -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.OpenIdConnect; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Okta.AspNet.Abstractions; namespace Okta.AspNetCore @@ -32,42 +29,14 @@ public static AuthenticationBuilder AddOktaMvc(this AuthenticationBuilder builde private static AuthenticationBuilder AddCodeFlow(AuthenticationBuilder builder, OktaMvcOptions options) { - var issuer = UrlHelper.CreateIssuerUrl(options.OktaDomain, options.AuthorizationServerId); - - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - builder.AddOpenIdConnect(oidcOptions => + var events = new OpenIdConnectEvents { - oidcOptions.ClientId = options.ClientId; - oidcOptions.ClientSecret = options.ClientSecret; - oidcOptions.Authority = issuer; - oidcOptions.CallbackPath = new PathString(options.CallbackPath); - oidcOptions.SignedOutCallbackPath = new PathString(OktaDefaults.SignOutCallbackPath); - oidcOptions.ResponseType = OpenIdConnectResponseType.Code; - oidcOptions.GetClaimsFromUserInfoEndpoint = options.GetClaimsFromUserInfoEndpoint; - oidcOptions.SecurityTokenValidator = new StrictSecurityTokenValidator(); - oidcOptions.SaveTokens = true; - oidcOptions.UseTokenLifetime = false; - oidcOptions.BackchannelHttpHandler = new UserAgentHandler("okta-aspnetcore", typeof(OktaAuthenticationOptionsExtensions).Assembly.GetName().Version); - - var hasDefinedScopes = options.Scope?.Any() ?? false; - if (hasDefinedScopes) - { - oidcOptions.Scope.Clear(); - foreach (var scope in options.Scope) - { - oidcOptions.Scope.Add(scope); - } - } + OnRedirectToIdentityProvider = BeforeRedirectToIdentityProviderAsync, + }; - oidcOptions.TokenValidationParameters = new DefaultTokenValidationParameters(options, issuer) - { - ValidAudience = options.ClientId, - NameClaimType = "name", - }; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); - oidcOptions.Events.OnRedirectToIdentityProvider = BeforeRedirectToIdentityProviderAsync; - }); + builder.AddOpenIdConnect(oidcOptions => OpenIdConnectOptionsHelper.ConfigureOpenIdConnectOptions(options, events, oidcOptions)); return builder; } diff --git a/Okta.AspNetCore/OktaMvcOptions.cs b/Okta.AspNetCore/OktaMvcOptions.cs index 7cef0b2..76614e2 100644 --- a/Okta.AspNetCore/OktaMvcOptions.cs +++ b/Okta.AspNetCore/OktaMvcOptions.cs @@ -15,6 +15,8 @@ public class OktaMvcOptions : AspNet.Abstractions.OktaWebOptions public string CallbackPath { get; set; } = OktaDefaults.CallbackPath; + public string PostLogoutRedirectUri { get; set; } + public IList Scope { get; set; } = OktaDefaults.Scope; public bool GetClaimsFromUserInfoEndpoint { get; set; } = false; diff --git a/Okta.AspNetCore/OpenIdConnectOptionsHelper.cs b/Okta.AspNetCore/OpenIdConnectOptionsHelper.cs new file mode 100644 index 0000000..8c8167f --- /dev/null +++ b/Okta.AspNetCore/OpenIdConnectOptionsHelper.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) 2018-present Okta, Inc. All rights reserved. +// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information. +// + +using System.Linq; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Okta.AspNet.Abstractions; + +namespace Okta.AspNetCore +{ + public class OpenIdConnectOptionsHelper + { + /// + /// Configure an OpenIdConnectOptions object based on user's configuration. + /// + /// The options. + /// The OpenIdConnect events. + /// The OpenIdConnectOptions to configure. + public static void ConfigureOpenIdConnectOptions(OktaMvcOptions oktaMvcOptions, OpenIdConnectEvents events, OpenIdConnectOptions oidcOptions) + { + var issuer = UrlHelper.CreateIssuerUrl(oktaMvcOptions.OktaDomain, oktaMvcOptions.AuthorizationServerId); + + oidcOptions.ClientId = oktaMvcOptions.ClientId; + oidcOptions.ClientSecret = oktaMvcOptions.ClientSecret; + oidcOptions.Authority = issuer; + oidcOptions.CallbackPath = new PathString(oktaMvcOptions.CallbackPath); + oidcOptions.SignedOutCallbackPath = new PathString(OktaDefaults.SignOutCallbackPath); + oidcOptions.SignedOutRedirectUri = oktaMvcOptions.PostLogoutRedirectUri; + oidcOptions.ResponseType = OpenIdConnectResponseType.Code; + oidcOptions.GetClaimsFromUserInfoEndpoint = oktaMvcOptions.GetClaimsFromUserInfoEndpoint; + oidcOptions.SecurityTokenValidator = new StrictSecurityTokenValidator(); + oidcOptions.SaveTokens = true; + oidcOptions.UseTokenLifetime = false; + oidcOptions.BackchannelHttpHandler = new UserAgentHandler( + "okta-aspnetcore", + typeof(OktaAuthenticationOptionsExtensions).Assembly.GetName().Version); + + var hasDefinedScopes = oktaMvcOptions.Scope?.Any() ?? false; + if (hasDefinedScopes) + { + oidcOptions.Scope.Clear(); + foreach (var scope in oktaMvcOptions.Scope) + { + oidcOptions.Scope.Add(scope); + } + } + + oidcOptions.TokenValidationParameters = new DefaultTokenValidationParameters(oktaMvcOptions, issuer) + { + ValidAudience = oktaMvcOptions.ClientId, + NameClaimType = "name", + }; + + oidcOptions.Events.OnRedirectToIdentityProvider = events.OnRedirectToIdentityProvider; + } + } +}