Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,16 @@ Please find the sample that fits your use-case from the table below.

## Contributing

We're happy to accept contributions and PRs! Please see the [contribution guide](CONTRIBUTING.md) to understand how to structure a contribution.
We're happy to accept contributions and PRs! Please see the [contribution guide](CONTRIBUTING.md) to understand how to structure a contribution.


## Okta-Hosted-Login with dotnet48 MVC webapp and Okta OIDC
This webapp is able to authenticate with Okta and fetch back the user claims and id_token payload in the Owin context. However, the below issues are present and will not be readily resolved (MSFT Owin framework issue). Therefore, we would **not** recommend using dotnet48 with Okta OIDC. Instead, please upgrade to dotnet core to use Okta OIDC or if that is not possible, use dotnet48 with Okta SAML.

## Known Issues
1. The Owin context does not contain the access token. It is null at runtime. This is a known issue with the Owin framework and is not resolved by Microsoft. Therefore, the access token cannot be fetched from the Owin context. If you need to call external APIs, this will be an issue. The proposed solution here is to create an OktaAdapter that will fetch and validate the access token.
2. Global signout does not work. This app's signout will result in a redirect to the global Okta org's configured error page. Instead, you may need to try Okta's Single Logout URL (see link below) or manually clearing the cookies, Okta session, and local session. If using JWT to manage user session, configure a low expiry access token and long refresh token approach.
3. Okta's prescribed solution with app.UseOktaMvc in Startup.cs does not work. It will result in an infinite redirect loop between the webapp and Okta's AuthZ server due to a thrown error. This looks to be an issue with Okta's aspnet library and dotnet48 Owin's middleware. Instead, I modified the service to instead call app.UseOpenIdConnectAuthentication directly - which works. I believe the user claims are pulled from the user-info endpoint though and not the id_token.

## References
- [Okta Single Logout](https://help.okta.com/en-us/content/topics/apps/apps_single_logout.htm)
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,18 @@ public ActionResult Contact()
[Authorize]
public ActionResult Profile()
{
return View(HttpContext.GetOwinContext().Authentication.User.Claims);
var user = HttpContext.GetOwinContext().Authentication.User;
var claims = user.Claims;
var accessToken = user.Claims.FirstOrDefault(c => c.Type == "access_token")?.Value;
var idToken = user.Claims.FirstOrDefault(c => c.Type == "id_token")?.Value;
Console.WriteLine("This is accessToken: " + accessToken); // this will be null
Console.WriteLine("This is idToken: " + idToken); // this will be null

ViewBag.AccessToken = accessToken;
ViewBag.IdToken = idToken;

return View(claims);
}

}
}
132 changes: 123 additions & 9 deletions okta-hosted-login/okta-aspnet-mvc-example/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web.Services.Description;
using Antlr.Runtime;
using IdentityModel.Client;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Okta.AspNet;
using Owin;
using static IdentityModel.OidcConstants;

[assembly: OwinStartup(typeof(okta_aspnet_mvc_example.Startup))]
[assembly: OwinStartup(typeof(dotnet48_okta_oidc_webapp.Startup))]

namespace okta_aspnet_mvc_example
namespace dotnet48_okta_oidc_webapp
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Enable TLS 1.2
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;

/*
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

//app.UseCookieAuthentication(new CookieAuthenticationOptions()
//{
// LoginPath = new PathString("/Account/Login"),
//});

app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/Login")
});

app.UseOktaMvc(new OktaMvcOptions()
{
OktaDomain = ConfigurationManager.AppSettings["okta:OktaDomain"],
Expand All @@ -26,9 +52,97 @@ public void Configuration(IAppBuilder app)
AuthorizationServerId = ConfigurationManager.AppSettings["okta:AuthorizationServerId"],
RedirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"],
PostLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"],
GetClaimsFromUserInfoEndpoint = true,
Scope = new List<string> {"openid", "profile", "email"},
Scope = new List<string> { "openid", "profile", "email" },
//LoginMode = LoginMode.SelfHosted,
});
*/
ConfigureAuth(app);


}
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
});

var clientId = ConfigurationManager.AppSettings["okta:ClientId"].ToString();
var clientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"].ToString();
var issuer = ConfigurationManager.AppSettings["okta:Issuer"].ToString();
var redirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"].ToString();
var postLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"].ToString();
UserInfoResponse userInfoResponse;

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
ClientSecret = clientSecret,
Authority = issuer,
RedirectUri = redirectUri,
ResponseType = "id_token token",
UseTokenLifetime = false,
Scope = "openid profile email",
PostLogoutRedirectUri = postLogoutRedirectUri,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = context =>
{
if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idToken = context.OwinContext.Authentication.User.Claims.FirstOrDefault(c => c.Type == "id_token")?.Value;
context.ProtocolMessage.IdTokenHint = idToken;
}

return Task.FromResult(true);
},
AuthorizationCodeReceived = async context =>
{
// Exchange code for access and ID tokens
var tokenClient = new TokenClient(
issuer + "/v1/token", clientId, clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(context.ProtocolMessage.Code, redirectUri);

if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}

var userInfoClient = new UserInfoClient(issuer + "/v1/userinfo");
userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);

var identity = new ClaimsIdentity();
identity.AddClaims(userInfoResponse.Claims);
identity.AddClaim(new Claim("id_token", tokenResponse.IdentityToken));
identity.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
identity.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
}

var nameClaim = new Claim(ClaimTypes.Name, userInfoResponse.Claims.FirstOrDefault(c => c.Type == "name")?.Value);
identity.AddClaim(nameClaim);


context.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(identity.Claims, context.AuthenticationTicket.Identity.AuthenticationType),
context.AuthenticationTicket.Properties);

Console.WriteLine("This is tokenResponse.AccessToken: ");
Console.WriteLine(tokenResponse.AccessToken);
Trace.WriteLine("This is tokenResponse.AccessToken: ");
Trace.WriteLine(tokenResponse.AccessToken);
}
}
});
Console.WriteLine("Okta OpenID Connect middleware registered.");
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

<h2>@ViewBag.Title</h2>

<dl class="dl-horizontal">
@if (Context.User.Identity.IsAuthenticated)
{
<dl class="dl-horizontal">
@foreach (var claim in Model)
{
<dt title="@claim.Type">
Expand All @@ -22,4 +24,9 @@

<dd id="@String.Format("claim-{0}", claim.Type)">@claim.Value</dd>
}
</dl>
</dl>
} else {
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("Log in", "Login", "Account")</li>
</ul>
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
<!DOCTYPE html>
<html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - My ASP.NET Application</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
</head>

<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
Expand All @@ -22,25 +24,26 @@
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Profile", "Profile", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
@if (Context.User.Identity.IsAuthenticated)
{
<ul class="nav navbar-nav navbar-right">
<li>
<p class="navbar-text">Hello, <b>@Context.User.Identity.Name</b></p>
</li>
<li>@Html.ActionLink("Profile", "Profile", "Home", null, new {id = "profile-button" })</li>
<li>
<a id="logout-button" onclick="document.getElementById('logout_form').submit();" style="cursor: pointer;">Log out</a>
</li>
</ul>
<form action="/Account/Logout" method="post" id="logout_form"></form>
{
<ul class="nav navbar-nav navbar-right">
<li>
<p class="navbar-text">Hello, <b>@Context.User.Identity.Name</b></p>
</li>
<li>
<a onclick="document.getElementById('logout_form').submit();" style="cursor: pointer;">Log
out</a>
</li>
</ul>
<form action="/Account/Logout" method="post" id="logout_form"></form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li>@Html.ActionLink("Log in", "Login", "Account", null, new {id = "login-button" })</li>
<li>@Html.ActionLink("Log in", "Login", "Account")</li>
</ul>
}
</div>
Expand All @@ -58,4 +61,5 @@
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
</body>
</html>

</html>
Loading