WSFederation Sign-in - Asp.net 4.6.1 - asp.net

So I'm trying to sort out web-based authentication using the WSFederation protocal. We've sorted out the setup, and my web app can reach the login page, after some headache:
(Asp.net on-premises authentication - The remote certificate is invalid according to the validation procedure)
Now I'm getting a 'IDX10201: None of the the SecurityTokenHandlers could read the 'securityToken' error. From what I understand, we'll need middleware to deal with the security tokens. So I'm trying to get started with this:
https://www.scottbrady91.com/Katana/WS-Federation-Token-Encryption-using-Microsoft-Katana
So I've set the TokenValidationParameters option in the WsFederationAuthenticationOptions, but I'm getting an error from VisualStudio saying that 'Cert' does not exist in the current context. I'm confused as to why, as my code is nearly identical to the guides.
I'm also wondering if our certificate has simply been improperly configured. I came across some SSL guidelines for ADFS, and I know our IT guy hasn't gone down that road (yet). I'd like to rule that out as a possible cause, but if someone knows that it is, or is not, the cause, it'd save me time and be greatly appreciated.
EDIT: After some reading, there are some things that are unclear to me? We're using an ADFS server to handle the credentials, but as I understand it, ADFS should also handle our tokens without any additional work. Am I wrong? Should I be using middleware? Or is there a problem with the ADFS server configuration?
using System;
using System.Collections.Generic;
using System.Configuration;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.WsFederation;
using Owin;
using System.Security.Cryptography.X509Certificates;
using System.Security;
using System.Net.Security;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.IdentityModel.Tokens;
using Microsoft.Owin;
using RCHHRATool;
using System.Net;
using System.IdentityModel.Selectors;
namespace RCHHRATool
{
public partial class Startup
{
private static string realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
private static string adfsMetadata = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
private X509Certificate2 certificate;
public void ConfigureAuth(IAppBuilder app)
{
Debug.WriteLine("Configure Auth Started");
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType =
WsFederationAuthenticationDefaults.AuthenticationType });
//System.Net.ServicePointManager.ServerCertificateValidationCallback.
//ServicePointManager.ServerCertificateValidationCallback = RemoteCertificateValidationCB;
var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach(X509Certificate2 cert in store.Certificates)
{
Debug.WriteLine(cert.Issuer);
if (cert.Issuer.Equals("CN=xxxxx.xxxxx.com"))
{
this.certificate = new X509Certificate2(cert);
}
}
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
SignInAsAuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
Wtrealm = "https://localhost:44340",
Wreply = "http://localhost:56879",
MetadataAddress = adfsMetadata,
AuthenticationType = "adfs",
SecurityTokenHandlers = new SecurityTokenHandlerCollection
{
new X509SecurityTokenHandler
{
Configuration = new SecurityTokenHandlerConfiguration
{
IssuerTokenResolver = new X509CertificateStoreTokenResolver(StoreName.Root,
StoreLocation.LocalMachine)
}
}
}
//},
//TokenValidationParameters = new TokenValidationParameters
//{
// ValidAudience = "https://localhost:44340/",
// ValidIssuer = "xxxxx.xxxxx.com",
// IssuerSigningToken = new X509SecurityToken(this.certificate)
//}
});
}

Turns out that asp.net (framework 4.6.1) & ws-federation doesn't handle encrypted security tokens out of the box. I followed a great guide to resolve the token error. After some tuning (watch your certificate footprint, and make sure your certificates are in trusted root), I managed to get the authentication working.

Related

OpenID with keycloak, infinite redirect loop after successful login ASP.NET MVC 4.7

I have setup my ASP.NET MVC 4.7 application like this.
Aside from the files bello, nothing has been changed from the original generated project.
The thing is, I can successfuly redirect to my Keycloak login page, but when it redirects to the url specified after successful login, it reroutes back to the Identity server (which is keycloak) and the identity server reroutes back to the reroute URL.
Here is the dev tools log, it does look like the cookies and sessions are passed properly
After successful login in Keycloak page, it redirects to /home which is correct as that is what I set
It does looks like cookies are passed properly:
However, it does seem that after calling /home (redirect) it calls the authentication again in Keycloak
This is causing an infinite loop. As authentication will then call /home and home calls the authentication again and again.
I already tried the approaches I found in the internet including using UseKentorOwinCookieSaver, using SystemWebCookieManager, and anything I tried online with no luck.
What am I missing here? Help help, I've been stuck on this issue for days now.
Here is the code
Startup.cs
using Microsoft.Owin;
using Owin;
using System;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Owin.Security.Keycloak;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.IdentityModel.Tokens;
using Microsoft.Owin.Host.SystemWeb;
[assembly: OwinStartup(typeof(AspNetMVC4.Startup))]
namespace AspNetMVC4
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseKentorOwinCookieSaver();
const string persistentAuthType = "keycloak_auth";
app.SetDefaultSignInAsAuthenticationType(persistentAuthType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = persistentAuthType,
AuthenticationMode = AuthenticationMode.Active,
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager()
});
var desc = new AuthenticationDescription();
desc.AuthenticationType = "keycloak_auth";
desc.Caption = "keycloak_auth";
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "Auth0",
Authority = "http://localhost:8080/auth/realms/master",
ClientId = "keycloakdemo",
ClientSecret = "tUM2gZiW5H3Lx2DQ4b5t4x5FzzrmADGi",
// RedirectUri = "http://localhost:44337/",
//PostLogoutRedirectUri = auth0PostLogoutRedirectUri,
RedirectUri = "https://localhost:44337/home",
ResponseType = OpenIdConnectResponseType.Code,
Scope = "openid profile email",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager(),
});
}
}
}
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace AspNetMVC4.Controllers
{
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
return View();
}
public ActionResult About()
{
bool flag = User.Identity.IsAuthenticated;
ViewBag.Message = "Your application description page.";
return View();
}
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
}
}
you need to make sure you use samesite=none and also use HTTPS to get the cookies to work.
I finally figured it out and sucesfuly integrated Keycloak to ASP.NET MVC 4.7,
I am posting my solution here to help those who will have the same set of issues i had.
The thing is, Keycloak and OWIN/OpenID is not integrated seemless in ASP.NET MVC frameworks libraries so what i dis is to manualy process everything including the User Identity, process the tokens and identity and use the tokens to retrieve the informations i need thru Keycloak own Rest API.
I have made a quick and dirty demo here:
https://github.com/ruellm/ASPNetMVC4-Keycloak
Hopefuly it can help a soul someboday, as I was stuck for almost 2 weeks and finally solved it.

deliver token manually (in addition to the owin configuration way)

I have actually an asp.net website application, that can deliver token to an user with the following way :
the user logs into the application, go to a specific page and obtains a clientid and a clientsecret.
then, he calls the following api "....api/token" by giving clientid and clientsecret (client credentials grant type) to get the token.
This is the associated code :
using Microsoft.Owin;
using Owin;
using System;
using Microsoft.Owin.Security.OAuth;
[assembly: OwinStartup(typeof(MyApp.Web.App_Start.OwinStartup))]
namespace MyApp.Web.App_Start
{
public class OwinStartup
{
public void Configuration(IAppBuilder app)
{
OwinWebApiStartup.Configuration(app);
}
}
}
public static class OwinWebApiStartup
{
public static void Configuration(IAppBuilder app)
{
var provider = //my provider implementation;
var oauthServerOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/api/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
Provider = provider,
};
app.UseOAuthAuthorizationServer(oauthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AccessTokenProvider = //my provider implementation,
});
}
}
This is working nicely. But I would like to add a new feature, where a javascript client code, not an user anymore, would like to call my apis, and so it will need to have a token, but do not have a clientid and clientsecret.
This is my idea :
Create a new api endpoint, (only this one will be reachable by my javascript client code without token, and there, the code will generate a token (thanks to the username of the current user connected) and return this one (that will
be the same that an user could have obtained with the existing method) to be used by the javascript client code
I faced this problem in the past. I solved this via querystring, cause owin could only provide one token ressource. In fact it makes sense to rely on owin and on not implementing your own code.
My pseudo solution:
KeyValuePair<string, string[]> typePair = ctx.Request.Query.FirstOrDefault(x => x.Key == "type");
LoginType? loginType = GetLoginType(typePair);
[...]
switch (loginType)
{
case LoginType.User:
[...]
////within this routine you could set your claims depending on your needs
If you get another solution, I'd be grateful for sharing

Converting node.js Jwt example to .Net

I am trying to use a web api. They require that I use Jwt tokens. I am trying to do this in a asp.net 4.5.2 web app on azure. I have downloaded the Microsoft.IdentityModel.Tokens Nuget package and would like to use it to generate the needed tokens.
The web api I am trying to use has an example in node.js on how to generate the proper token. Here is their example:
var jwt = require('jsonwebtoken');
var payload = {
iss: api_key,
exp: ((new Date()).getTime() + 5000)
};
//Automatically creates header, and returns JWT
var token = jwt.sign(payload, api_secret);
All of the examples for the nuget package do a ton more than what is shown in the node.js example. Things like creating a secret value, claims identity, signing credentials and all kinds of other stuff.
I have the api_key value and the api_secret value. Can someone please tell me how to do the same thing using the nuget package as what they show using node.js?
After doing some research I think I have a solution. This is the simplest it gets in c# unfortunately, but I think its a passable solution considering we are talking apples and oranges. It seems C# is twice as verbose about the options and doesn't have sensible defaults for anything.
using System;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
namespace stackoverflow
{
class Program
{
static void Main(string[] args)
{
String api_key = "apiKey123123";
String api_secret = "401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429090fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1";
var signingKey = Convert.FromBase64String(api_secret);
JwtHeader jwtHeader = new JwtHeader(
new SigningCredentials(
new SymmetricSecurityKey(signingKey),
SecurityAlgorithms.HmacSha256Signature
)
);
JwtPayload jwtPayload = new JwtPayload {
{"iss", api_key},
{"exp", ((DateTimeOffset)DateTime.UtcNow).AddMilliseconds(5000).ToUnixTimeMilliseconds() }
};
var jwt = new JwtSecurityToken(jwtHeader, jwtPayload);
var jwtHandler = new JwtSecurityTokenHandler();
Console.Write(jwtHandler.WriteToken(jwt));
Console.ReadLine();
}
}
}
Only import was this package:
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.1" />

Implementing ASP.Net Web Forms App with Identity Server 3 (possibly Thinktecture or not?)

I've been attempting to implement a working solution into our .Net 4.5.2 web site project. My goal is to convert my web site project, which is a web forms application (not MVC), into using identity server 3 (possibly via thinktecture's implementation) to authenticate/authorize users.
Is this possible? How will the site life cycle change or can it say the same with a session object? Can I use identity server to gain the access token and then setup a session, and only request a new access token when the session expires or the access token expires?
Some Back Story:
I do have a working identity server 3 setup at the following location https://localhost:44399. Which responds with all the appropriate calls and limitations when I use a straight html file, but when I tried to implement the same thing in the login page (portal.aspx) it seems to forget the user and does not behave the same. The website (the client) is setup on https://localhost:9898. And the login page is https://localhost:9898/portal.aspx.
I'm using the oidc-client.js on the portal.aspx page, but I keep getting CORS (cross-browser origin scripting) errors when it is trying to authenticate. Is this an issue with web forms, this does not happen when using a regular html page?
This is a .net 4.5.2 "web site project" (not a "web application project") which uses web forms, not MVC. I have got many demo MVC applications to work correctly, but when I try to take the knowledge I have to the web forms application, I see that I really don't have a grasp on what is going on.
I have added the following nuget packages:
Microsoft.Bcl v1.1.10
Microsoft.Owin v3.0.1
Microsoft.Bcl.Async v1.0.168
System.IdentityModel.Tokens.Jwt v5.1.0
Newtonsoft.Json v9.0.1
Owin v1.0.0
Microsoft.IdentityModel.Logging v1.1.0
Microsoft.Owin.Host.SystemWeb v3.0.1
System.IdentityModel.Tokens.Jwt v5.1.0
Microsoft.IdentityModel.Tokens v5.1.0
Microsoft.Bcl.Build v1.0.21
Thinktecture.IdentityModel v3.6.1
When I attempt to add the startup.cs class it tells me that these classes are typically added to the App_Code directory, and do I want to put it there? I have tried both the root and the App_Code directory and neither seem to register when they are being called. I have added the package, thinking that this is what does the call:
Microsoft.Owin.Host.SystemWeb
But it seems like the startup class is not registered. So how do I get this to register in the pipeline so that 401 not authorized errors will send the user to the identity server?
Here is my startup class:
using ExpenseTracker.WebClient.Helpers;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Newtonsoft.Json.Linq;
using Owin;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Web;
using System.Web.Helpers;
using Thinktecture.IdentityModel.Client;
[assembly: OwinStartup(typeof(ExpenseTracker.WebClient.Startup))]
namespace ExpenseTracker.WebClient
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
AntiForgeryConfig.UniqueClaimTypeIdentifier = "unique_user_key";
app.UseResourceAuthorization(new AuthorizationManager());
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "mvc",
Authority = ExpenseTrackerConstants.IdSrv,
RedirectUri = ExpenseTrackerConstants.ExpenseTrackerClient,
SignInAsAuthenticationType = "Cookies",
ResponseType = "code id_token token",
Scope = "openid profile roles expensetrackerapi offline_access",
Notifications = new OpenIdConnectAuthenticationNotifications()
{
MessageReceived = async n =>
{
EndpointAndTokenHelper.DecodeAndWrite(n.ProtocolMessage.IdToken);
EndpointAndTokenHelper.DecodeAndWrite(n.ProtocolMessage.AccessToken);
//var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(n.ProtocolMessage.AccessToken);
},
SecurityTokenValidated = async n =>
{
var userInfo = await EndpointAndTokenHelper.CallUserInfoEndpoint(n.ProtocolMessage.AccessToken);
// use the authorization code to get a refresh token
var tokenEndpointClient = new OAuth2Client(
new Uri(ExpenseTrackerConstants.IdSrvToken),
"mvc", "secret");
var tokenResponse = await tokenEndpointClient.RequestAuthorizationCodeAsync(
n.ProtocolMessage.Code, ExpenseTrackerConstants.ExpenseTrackerClient);
var givenNameClaim = new Claim(
Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
userInfo.Value<string>("given_name"));
var familyNameClaim = new Claim(
Thinktecture.IdentityModel.Client.JwtClaimTypes.FamilyName,
userInfo.Value<string>("family_name"));
var roles = userInfo.Value<JArray>("role").ToList();
var newIdentity = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Thinktecture.IdentityModel.Client.JwtClaimTypes.GivenName,
Thinktecture.IdentityModel.Client.JwtClaimTypes.Role);
newIdentity.AddClaim(givenNameClaim);
newIdentity.AddClaim(familyNameClaim);
foreach (var role in roles)
{
newIdentity.AddClaim(new Claim(
Thinktecture.IdentityModel.Client.JwtClaimTypes.Role,
role.ToString()));
}
var issuerClaim = n.AuthenticationTicket.Identity
.FindFirst(Thinktecture.IdentityModel.Client.JwtClaimTypes.Issuer);
var subjectClaim = n.AuthenticationTicket.Identity
.FindFirst(Thinktecture.IdentityModel.Client.JwtClaimTypes.Subject);
newIdentity.AddClaim(new Claim("unique_user_key",
issuerClaim.Value + "_" + subjectClaim.Value));
newIdentity.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
newIdentity.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
newIdentity.AddClaim(new Claim("expires_at",
DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
n.AuthenticationTicket = new AuthenticationTicket(
newIdentity,
n.AuthenticationTicket.Properties);
},
}
});
}
}
}
And to make things worse, adding Microsoft.Owin.Host.SystemWeb does not compile due to strange errors like:
The type or namespace name 'SessionState' does not exist in the
namespace 'System.Web' (are you missing an assembly reference?)

Consume ProjectData from Project Server 2013 Workflow - Forbidden

I need to consume data from http://project/pwa/_api/ProjectData Project OData service from Project Server 2013 Workflow.
But I got "Forbidden" Response Code.
User have all rights (Administrator, Site Collection Administrator).
Consuming other endpoints (ProjectServer, Web, Lists) successfull, even from other site collections and farms.
When I need configure a security to successfully consume ProjectData?
Thank you!
Have you tried to provide credentials
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Text;
using Microsoft.SharePoint.Client;
using Microsoft.ProjectServer.Client;
static void Main(string[] args)
{
const string pwaPath = "http://ServerName/pwa/";
const string password = "pwa_password";
const string userName = "user_name#your_company.onmicrosoft.com";
var projContext = new ProjectContext(pwaPath);
var secureString = new SecureString();
password.ToCharArray().ToList().ForEach(x => secureString.AppendChar(x));
projContext.Credentials = new SharePointOnlineCredentials(userName, secureString);
// Get the list of published projects in Project Web App.
projContext.Load(projContext.Projects);
projContext.ExecuteQuery();
Console.WriteLine("\nThere are {0} projects", projContext.Projects);
}

Resources