Skip to content

Commit

Permalink
Add support for login_hint param (#147)
Browse files Browse the repository at this point in the history
* Add support for login-hint.
  • Loading branch information
laura-rodriguez authored Sep 21, 2020
1 parent ce76bcf commit 156466d
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 55 deletions.
35 changes: 35 additions & 0 deletions Okta.AspNet.Abstractions/OktaParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// <copyright file="OktaParams.cs" company="Okta, Inc">
// 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.
// </copyright>

using System.Collections.Generic;

namespace Okta.AspNet.Abstractions
{
/// <summary>
/// Well-known parameters used by Okta authentication.
/// </summary>
public static class OktaParams
{
/// <summary>
/// Used to pass an Okta one-time session token. Session tokens can be obtained via the Authentication API.
/// </summary>
public const string SessionToken = "sessionToken";

/// <summary>
/// Used to prepopulate a username if prompting for authentication.
/// </summary>
public const string LoginHint = "login_hint";

/// <summary>
/// Used to provide an identity provider if there is no Okta Session.
/// </summary>
public const string Idp = "idp";

/// <summary>
/// A list with all Okta well-known params.
/// </summary>
public static readonly IList<string> AllParams = new List<string>() { SessionToken, Idp, LoginHint };
}
}
7 changes: 6 additions & 1 deletion Okta.AspNet.Test/OktaHttpMessageHandlerShould.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using System;
// <copyright file="OktaHttpMessageHandlerShould.cs" company="Okta, Inc">
// 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.
// </copyright>

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
Expand Down
2 changes: 1 addition & 1 deletion Okta.AspNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Okta.AspNetCore.Mvc.Integra
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Okta.AspNetCore.WebApi.IntegrationTest", "Okta.AspNetCore.WebApi.IntegrationTest\Okta.AspNetCore.WebApi.IntegrationTest.csproj", "{C15647D6-77FC-43D6-86AD-888025A0326F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Okta.AspNet.Abstractions.Test", "Okta.AspNet.Abstractions.Test\Okta.AspNet.Abstractions.Test.csproj", "{265F81BE-5E48-4D90-851E-659FF93D4FC5}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Okta.AspNet.Abstractions.Test", "Okta.AspNet.Abstractions.Test\Okta.AspNet.Abstractions.Test.csproj", "{265F81BE-5E48-4D90-851E-659FF93D4FC5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
1 change: 1 addition & 0 deletions Okta.AspNet/OktaMvcOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public class OktaMvcOptions : Abstractions.OktaWebOptions
/// <summary>
/// Gets or sets the event invoked if exceptions are thrown during request processing.
/// </summary>
/// <value>The AuthenticationFailed event.</value>
public Func<AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions>, Task> AuthenticationFailed { get; set; } = notification => Task.FromResult(0);
}
}
20 changes: 8 additions & 12 deletions Okta.AspNet/OpenIdConnectAuthenticationOptionsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,23 +90,19 @@ private Task BeforeRedirectToIdentityProviderAsync(RedirectToIdentityProviderNot
}
}

// Add sessionToken to provide custom login
// Verify if additional well-known params (e.g login-hint, sessionToken, idp, etc.) should be sent in the request.
if (redirectToIdentityProviderNotification.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
var sessionToken = string.Empty;
redirectToIdentityProviderNotification.OwinContext.Authentication.AuthenticationResponseChallenge?.Properties?.Dictionary?.TryGetValue("sessionToken", out sessionToken);
var oktaRequestParamValue = string.Empty;

if (!string.IsNullOrEmpty(sessionToken))
foreach (var oktaParamKey in OktaParams.AllParams)
{
redirectToIdentityProviderNotification.ProtocolMessage.SetParameter("sessionToken", sessionToken);
}

var idpId = string.Empty;
redirectToIdentityProviderNotification.OwinContext.Authentication.AuthenticationResponseChallenge?.Properties?.Dictionary?.TryGetValue("idp", out idpId);
redirectToIdentityProviderNotification.OwinContext.Authentication.AuthenticationResponseChallenge?.Properties?.Dictionary?.TryGetValue(oktaParamKey, out oktaRequestParamValue);

if (!string.IsNullOrEmpty(idpId))
{
redirectToIdentityProviderNotification.ProtocolMessage.SetParameter("idp", idpId);
if (!string.IsNullOrEmpty(oktaRequestParamValue))
{
redirectToIdentityProviderNotification.ProtocolMessage.SetParameter(oktaParamKey, oktaRequestParamValue);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Okta.AspNet.Abstractions;

namespace Okta.AspNetCore.Mvc.IntegrationTest.Controllers
{
Expand Down Expand Up @@ -32,6 +33,20 @@ public IActionResult LoginWithIdp()
return RedirectToAction("Index", "Home");
}

public IActionResult LoginWithLoginHint()
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Items.Add(OktaParams.LoginHint, "foo");
properties.RedirectUri = "/Home/";

return Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
}

return RedirectToAction("Index", "Home");
}

[HttpPost]
public IActionResult Logout()
{
Expand Down
13 changes: 12 additions & 1 deletion Okta.AspNetCore.Mvc.IntegrationTest/OktaMiddlewareShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public async Task RedirectWhenAccessToProtectedRouteWithoutSignedInAsync()
public async Task IncludeIdpInAuthorizeUrlAsync()
{
var loginWithIdpEndpoint = string.Format("{0}/Account/LoginWithIdp", BaseUrl);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, loginWithIdpEndpoint);
using (var client = _server.CreateClient())
{
var response = await client.GetAsync(loginWithIdpEndpoint);
Expand All @@ -56,6 +55,18 @@ public async Task IncludeIdpInAuthorizeUrlAsync()
}
}

[Fact]
public async Task IncludeLoginHintInAuthorizeUrlAsync()
{
var loginWithLoginHintEndpoint = string.Format("{0}/Account/LoginWithLoginHint", BaseUrl);
using (var client = _server.CreateClient())
{
var response = await client.GetAsync(loginWithLoginHintEndpoint);
Assert.True(response.StatusCode == System.Net.HttpStatusCode.Found);
Assert.Contains("login_hint=foo", response.Headers.Location.AbsoluteUri);
}
}

public void Dispose()
{
_server.Dispose();
Expand Down
51 changes: 24 additions & 27 deletions Okta.AspNetCore.Test/OktaHttpMessageHandlerShould.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Okta.AspNet.Test
public class OktaHttpMessageHandlerShould
{
[Fact]
public async Task SetInnerHandlerWebProxy()
public void SetInnerHandlerWebProxy()
{
var testProxyAddress = "http://test.cxm/";
var testUserName = "testUserName";
Expand Down Expand Up @@ -44,40 +44,37 @@ public async Task SetInnerHandlerWebProxy()
}

[Fact]
public async Task SetInnerHandlerWebProxyPort()
public void SetInnerHandlerWebProxyPort()
{
await Task.Run(() =>
var testProxyBaseUri = "http://test.cxm/";
var testPort = 8080;
var expectedProxyAddress = "http://test.cxm:8080/";
var testFrameworkName = $"{nameof(SetInnerHandlerWebProxyPort)}_testFrameworkName";
var version = typeof(OktaHttpMessageHandlerShould).Assembly.GetName().Version;
var oktaHandler = new OktaHttpMessageHandler(testFrameworkName, version, new OktaMvcOptions
{
var testProxyBaseUri = "http://test.cxm/";
var testPort = 8080;
var expectedProxyAddress = "http://test.cxm:8080/";
var testFrameworkName = $"{nameof(SetInnerHandlerWebProxyPort)}_testFrameworkName";
var version = typeof(OktaHttpMessageHandlerShould).Assembly.GetName().Version;
var oktaHandler = new OktaHttpMessageHandler(testFrameworkName, version, new OktaMvcOptions
Proxy = new ProxyConfiguration
{
Proxy = new ProxyConfiguration
{
Host = testProxyBaseUri,
Port = testPort,
},
});
oktaHandler.InnerHandler.Should().NotBeNull();
oktaHandler.InnerHandler.Should().BeAssignableTo<HttpClientHandler>();
Host = testProxyBaseUri,
Port = testPort,
},
});
oktaHandler.InnerHandler.Should().NotBeNull();
oktaHandler.InnerHandler.Should().BeAssignableTo<HttpClientHandler>();

var httpClientHandler = (HttpClientHandler)oktaHandler.InnerHandler;
httpClientHandler.Proxy.Should().NotBeNull();
httpClientHandler.Proxy.Should().BeAssignableTo<DefaultProxy>();
var httpClientHandler = (HttpClientHandler)oktaHandler.InnerHandler;
httpClientHandler.Proxy.Should().NotBeNull();
httpClientHandler.Proxy.Should().BeAssignableTo<DefaultProxy>();

var webProxy = (DefaultProxy)httpClientHandler.Proxy;
var proxyUri = webProxy.GetProxy(Arg.Any<Uri>());
proxyUri.ToString().Should().Be(expectedProxyAddress);
proxyUri.Port.Should().Be(testPort);
webProxy.Credentials.Should().BeNull();
});
var webProxy = (DefaultProxy)httpClientHandler.Proxy;
var proxyUri = webProxy.GetProxy(Arg.Any<Uri>());
proxyUri.ToString().Should().Be(expectedProxyAddress);
proxyUri.Port.Should().Be(testPort);
webProxy.Credentials.Should().BeNull();
}

[Fact]
public async Task NotSetInnerHandlerWebProxyIfNotSpecified()
public void NotSetInnerHandlerWebProxyIfNotSpecified()
{
var testFrameworkName = $"{nameof(NotSetInnerHandlerWebProxyIfNotSpecified)}_testFrameworkName";
var version = typeof(OktaHttpMessageHandlerShould).Assembly.GetName().Version;
Expand Down
18 changes: 7 additions & 11 deletions Okta.AspNetCore/OktaAuthenticationOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,16 @@ private static AuthenticationBuilder AddCodeFlow(AuthenticationBuilder builder,

private static Task BeforeRedirectToIdentityProviderAsync(RedirectContext context)
{
// Add sessionToken to provide custom login
if (context.Properties.Items.TryGetValue("sessionToken", out var sessionToken))
{
if (!string.IsNullOrEmpty(sessionToken))
{
context.ProtocolMessage.SetParameter("sessionToken", sessionToken);
}
}
// Verify if additional well-known params (e.g login-hint, sessionToken, idp, etc.) should be sent in the request.
var oktaRequestParamValue = string.Empty;

if (context.Properties.Items.TryGetValue("idp", out var idpId))
foreach (var oktaParamKey in OktaParams.AllParams)
{
if (!string.IsNullOrEmpty(idpId))
context.Properties?.Items?.TryGetValue(oktaParamKey, out oktaRequestParamValue);

if (!string.IsNullOrEmpty(oktaRequestParamValue))
{
context.ProtocolMessage.SetParameter("idp", idpId);
context.ProtocolMessage.SetParameter(oktaParamKey, oktaRequestParamValue);
}
}

Expand Down
27 changes: 26 additions & 1 deletion docs/aspnet4x-mvc.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,31 @@ public class Startup

> Note: If you are using role-based authorization and you need to redirect unauthorized users to an access-denied page or similar, check out [CookieAuthenticationProvider.ApplyRedirect](https://docs.microsoft.com/en-us/previous-versions/aspnet/mt152260(v%3Dvs.113)).
## Specifying the `login_hint` parameter

The `login_hint` parameter allows you to pass a username to prepopulate when prompting for authentication. For more details check out the [Okta documentation](https://developer.okta.com/docs/reference/api/oidc/#request-parameters).

Add the following action in your controller:

```csharp
public ActionResult Login(string loginHint)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Dictionary.Add(OktaParams.LoginHint, loginHint);
properties.RedirectUri = "/Home/About";

HttpContext.GetOwinContext().Authentication.Challenge(properties,
OktaDefaults.MvcAuthenticationType);

return new HttpUnauthorizedResult();
}

return RedirectToAction("Index", "Home");
}
```

## Login with an external identity provider

Add the following action in your controller:
Expand All @@ -114,7 +139,7 @@ public ActionResult LoginWithIdp(string idp)
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Dictionary.Add("idp", idp);
properties.Dictionary.Add(OktaParams.Idp, idp);
properties.RedirectUri = "/Home/About";

HttpContext.GetOwinContext().Authentication.Challenge(properties,
Expand Down
24 changes: 23 additions & 1 deletion docs/aspnetcore-mvc.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ public void ConfigureServices(IServiceCollection services)
```
> Note: If you are using role-based authorization and you need to redirect unauthorized users to an access-denied page or similar, check out [CookieAuthenticationOptions.AccessDeniedPath](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.cookies.cookieauthenticationoptions.accessdeniedpath?view=aspnetcore-2.2).
## Specifying the `login_hint` parameter

The `login_hint` parameter allows you to pass a username to prepopulate when prompting for authentication. For more details check out the [Okta documentation](https://developer.okta.com/docs/reference/api/oidc/#request-parameters).

Add the following action in your controller:

```csharp
public IActionResult SignInWithIdp(string loginHint)
{
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Items.Add(OktaParams.LoginHint, loginHint);
properties.RedirectUri = "/Home/";

return Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
}

return RedirectToAction("Index", "Home");
}
```

## Login with an external identity provider

Add the following action in your controller:
Expand All @@ -126,7 +148,7 @@ public IActionResult SignInWithIdp(string idp)
if (!HttpContext.User.Identity.IsAuthenticated)
{
var properties = new AuthenticationProperties();
properties.Items.Add("idp", idp);
properties.Items.Add(OktaParams.Idp, idp);
properties.RedirectUri = "/Home/";

return Challenge(properties, OktaDefaults.MvcAuthenticationScheme);
Expand Down

0 comments on commit 156466d

Please sign in to comment.