SSO Sustainsys.Saml2.Owin Request is not Authenticated - access_denied - c#

I have to do SSO authentication with saml2 for my existing asp.net web application.
I am using Sustainsys.Saml2.Owin example to do that.
Identity provider is Azure ADFS ( https://sts.windows.net/TENANTID )
I have configured the Startup file. It loads the metadata file and certificate.
And in my Login page, I am challenging if not authenticated.
It is successfully redirecting to the login page but the Request is never getting authenticated after the login. And in the reply URL we are getting error=access_denied
[neither Request.IsAuthenticated or owinContext.Authentication.User.Identity.IsAuthenticated is set to true]
So it keep on challenging for many times and error with bad request.
What I am doing wrong?
Which module of Owin/Sustainsys is reposnsible to set the IsAuthenticated status?
*a Saml2. cookie [Saml2.DAeP63c***UTX0h***_***] is passed along with the request after login into Microsoft [https://login.microsoftonline.com/TENANTID/saml2]
Startup.cs file
public void ConfigureAuth(IAppBuilder appBuilder)
{
try
{
appBuilder.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions());
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseSaml2Authentication(CreateSaml2Options());
}
catch (Exception exp)
{
}
}
private Saml2AuthenticationOptions CreateSaml2Options()
{
try
{
var spOptions = CreateSPOptions();
var Saml2AuthOptions = new Saml2AuthenticationOptions(false)
{
SPOptions = spOptions,
Notifications = new Saml2Notifications(),
};
var idp = new IdentityProvider(new EntityId(authority), spOptions)
{
MetadataLocation = metadataLocation,
Binding = Saml2BindingType.HttpRedirect
};
idp.SigningKeys.AddConfiguredKey(
new X509Certificate2(certificateLocation));
Saml2AuthOptions.IdentityProviders.Add(idp);
return Saml2AuthOptions;
}
catch (Exception exp)
{
}
}
private SPOptions CreateSPOptions()
{
try
{
var engAus = "en-AU";
var organization = new Organization();
var spOptions = new SPOptions
{
EntityId = new EntityId(ApplicationId),
ReturnUrl = new Uri(redirectUrl),
Organization = organization,
};
return spOptions;
}
catch (Exception exp)
{
}
}
Login.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
IOwinContext owinContext = HttpContext.Current.GetOwinContext();
//if (Request.IsAuthenticated)
if (owinContext.Authentication.User != null &&
owinContext.Authentication.User.Identity != null &&
owinContext.Authentication.User.Identity.IsAuthenticated)
{
//Authenticated
string name = owinContext.Authentication.User.Identity.Name;
}
else
{
var authenticationTypes = owinContext.Authentication.GetAuthenticationTypes().Select(d => d.AuthenticationType).ToArray();
owinContext.Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, authenticationTypes);
}
}
}

(all code posted here are the same sample from Github)
You need to understand how SAML works, here's a simple saml implementation class that I used before I dive into SustainsysSAML. AspNetSaml
This is the basic flow of SAML Implementation:
User access your app, if user is not yet authenticated your app should redirect the user to your saml provider.
//specify the SAML provider url here, aka "Endpoint"
var samlEndpoint = "http://saml-provider-that-we-use.com/login/";
var request = new AuthRequest(
"http://www.myapp.com", //put your app's "unique ID" here
"http://www.myapp.com/SamlConsume" //assertion Consumer Url - the redirect URL where the provider will send authenticated users
);
//generate the provider URL
string url = request.GetRedirectUrl(samlEndpoint);
//then redirect your user to the above "url" var
//for example, like this:
Response.Redirect(url);
From saml provider, user enters credentials and if valid user, saml provider will authenticate and redirect the user to your app.
SAML provider will post the samlresponse to your app (eg. http://www.myapp.com/SamlConsum).
//ASP.NET MVC action method... But you can easily modify the code for Web-forms etc.
public ActionResult SamlConsume()
{
//specify the certificate that your SAML provider has given to you
string samlCertificate = #"-----BEGIN CERTIFICATE-----
BLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAH123543==
-----END CERTIFICATE-----";
Saml.Response samlResponse = new Response(samlCertificate);
samlResponse.LoadXmlFromBase64(Request.Form["SAMLResponse"]); //SAML providers usually POST the data into this var
if (samlResponse.IsValid())
{
//WOOHOO!!! user is logged in
//YAY!
//Some more optional stuff for you
//lets extract username/firstname etc
string username, email, firstname, lastname;
try
{
username = samlResponse.GetNameID();
email = samlResponse.GetEmail();
firstname = samlResponse.GetFirstName();
lastname = samlResponse.GetLastName();
}
catch(Exception ex)
{
//insert error handling code
//no, really, please do
return null;
}
//user has been authenticated, put your code here, like set a cookie or something...
//or call FormsAuthentication.SetAuthCookie() or something
}
}
Your app will read the samlresponse and if valid will let the user use your app, your app will now handle the roles of the user depending on your policies.
Some tips:
Make sure your app is identifiable by your saml provider.
Use Firebug to trace your http requests (or any http tracing tool)
Understand the difference between samlresponse and samlrequest
Using Firebug you should be able to see the samlresponse.
If you have multiple web apps that you want to have SSO using your saml provider. I suggest you create an httprequest/httphandler to handle the samlresponse from your provider. You can then install this dll to your server and just add the handler to each web app's config. No code change require to your web apps :).
I hope this helps.

Related

Unable to login to AzureAD using WS-Federation SSO

I have old code for logging in with ADFS but now have an additional requirement for loggging in via AzureAD. The method in the Federation library which works fine with ADFS but not Azure AD is the following.
public static void RedirectToIdentityProvider(string db_, string realmIdentifier_, FederationConfig config_)
{
try
{
var returnurl = "http://localhost/6420";
FederationDatabaseFunctions db = new FederationDatabaseFunctions(db_);
FederationData federationData = db.GetFederatedDataByRealm(realmIdentifier_);
TrustedIssuerFederationMetadata metadata = new TrustedIssuerFederationMetadata(federationData);
WSFederationAuthenticationModule wsFAM = new WSFederationAuthenticationModule();
wsFAM.PassiveRedirectEnabled = true;
wsFAM.RequireHttps = true;
wsFAM.PersistentCookiesOnPassiveRedirects = false;
wsFAM.Issuer = "https://sts.windows.net/27010d87-5b00-4131-b6fd-fde8346ce198/";
wsFAM.Realm = "https://login.microsoftonline.com/";
wsFAM.RedirectToIdentityProvider(returnurl, returnurl, false);
}
catch { throw; }
}
I get a 404 error but the issuer is definitely correct. Is there any additional parameters to pass for AzureAD.
Note: I have managed to login with AzureAD in a separate MVC project using an OWIN startup but I ideally need to replicate using my existing setup if possible.
This is the url which gives a 404: This is the url which I get back: https://sts.windows.net/27010d87-5b00-4131-b6fd-fde8346ce198/?wa=wsignin1.0&wtrealm=https%3a%2f%2flogin.microsoftonline.com%2f27010d87-5b00-4131-b6fd-fde8346ce198%2f&wctx=rm%3d0%26id%3d&wct=2019-11-05T11%3a30%3a51Z&whr=https%3a%2f%2flogin.microsoftonline.com%2f27010d87-5b00-4131-b6fd-fde8346ce198%2f

MVC 5 C# Web App use forms or windows authentication against AD

I have an MVC5 Application working with forms authentication against AD. I would like to have the ability to use windows authentication if the user is already signed into AD on their machine and if not go to forms authentication where they can enter their AD credentials.
Is there a relatively straight forward way to handle this without writing a custom membership class? And if so how? Any help would be appreciated. I have seen several posts about not mixing the types of authentication but I don't use any local authentication. It is all against AD. Is this still not supported?
It's not technically supported by IIS (if that's what you're using), but you can enable both forms and windows authentication.
In whatever controller you are using in the "loginUrl" of your forms authentication settings (in your Web.config) you can check the headers to determine if the user is logged in, for instance:
Given this setting
<forms loginUrl="~/Login">
You can do this:
public class LoginController: Controller
{
public ActionResult Index()
{
string windowsUserName = Request.ServerVariables["LOGON_USER"];
if (!string.IsNullOrEmpty(windowsUserName))
{
Regex regex = new Regex(#"(^\w+)\\", RegexOptions.IgnoreCase);
string userName = regex.Replace(windowsUserName, string.Empty);
// validate the user name against ad here
FormsAuthentication.SetAuthCookie(userName, false);
this.RedirectToAction("Index", "Home");
}
else
{
// if the user isn't signed in with AD credentials you can send an
// "unauthorized" http code and the browser (excluding Firefox)
// will try to send credentials (if available).
// you will have to manage staying out of a redirect loop
// many options here: set and check a cookie, session, headers, etc.
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
}
}
}
An anonymous (unauthenticated) request hits the LoginController
System sends an unauthorized http response
An authenticated request hits the LoginController
System checks the header and compares the value to AD (example of comparing with AD below)
When I had to do this for a particular app, I also checked the requests host address (to make sure it was on the same domain as the server, otherwise I didn't even bother:
IPAddress address = null;
if (IPAddress.TryParse(Request.UserHostAddress, out address))
{
if (IPAddress.IsLoopback(address))
{
address = Dns.GetHostAddresses(Dns.GetHostName()).FirstOrDefault(ip => !IPAddress.IsLoopback(ip));
if (address == null)
return View();
}
IPHostEntry entry = Dns.GetHostEntry(address);
bool isPartOfDomain = false;
foreach (IPAddress hostEntryAddress in entry.AddressList)
{
if (String.Equals(address.ToString(), hostEntryAddress.ToString()))
{
string domain = "Your Domain Here"; // or get it from your configuration settings / db / etc
using (PrincipalContext domainContext = new PrincipalContext(ContextType.Domain, domain))
{
string computerName = entry.HostName.Replace("." + domain, string.Empty);
using (ComputerPrincipal computer = ComputerPrincipal.FindByIdentity(domainContext, computerName))
if (computer != null)
{
isPartOfDomain = true;
break;
}
}
}
}
}
I usually check against a Domain PrincipalContext to verify the user name.
DirectoryEntry entry;
string domain = "Your Domain Here",
userName = "Some User Name";
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domain))
{
using (UserPrincipal principal = UserPrincipal.FindByIdentity(context, userName))
{
if (principal != null && (entry = (DirectoryEntry)principal.GetUnderlyingObject()) != null)
{
string userPrincipalName = principal.UserPrincipalName ?? principal.Name;
userPrincipalName = userPrincipalName.Substring(0, (userPrincipalName.Contains("#") ? userPrincipalName.IndexOf("#") : userPrincipalName.IndexOf(" ")));
bool isValid = string.Equals(userName, userPrincipalName);
}
}
}

Oauth authentication with owin & Nancy

Following this guide for external auth using MVC 5 on Owin - External login providers with owinkatana.
I have added the following to my Owin Nancy application
Startup.cs -
app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = "ExternalCookie";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ExternalCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
app.UseTwitterAuthentication(new TwitterAuthenticationOptions
{
ConsumerKey = "mykey",
ConsumerSecret = "mypass"
});
LoginModule.cs (nancy module)
Post["ExternalLogin"] = _ =>
{
var provider = Request.Form.name;
var auth = Context.GetAuthenticationManager();
auth.Challenge(new AuthenticationProperties
{
RedirectUri = String.Format("/?provder={0}", provider)
}, provider);
return HttpStatusCode.Unauthorized;
};
Now at the challenge point here nothing happens whatsoever. It just shows a blank page with the Url of the redirect. I have confirmed that I can get it to work following the example in MVC.
Does anyone know the correct Nancy code for this section?
I'll expand on a comment I was about to leave and just make it an answer (even though you moved away from Nancy it seems). I asked a similar question, and was pointed to the following code example on github:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/tree/dev/samples/Nancy/Nancy.Client
Assuming you have your OIDC wired up properly in Startup.cs, the following code is what I needed to get Nancy module to trigger the authentication on my signin/signout routes:
namespace Nancy.Client.Modules {
public class AuthenticationModule : NancyModule {
public AuthenticationModule() {
Get["/signin"] = parameters => {
var manager = Context.GetAuthenticationManager();
if (manager == null) {
throw new NotSupportedException("An OWIN authentication manager cannot be extracted from NancyContext");
}
var properties = new AuthenticationProperties {
RedirectUri = "/"
};
// Instruct the OIDC client middleware to redirect the user agent to the identity provider.
// Note: the authenticationType parameter must match the value configured in Startup.cs
manager.Challenge(properties, OpenIdConnectAuthenticationDefaults.AuthenticationType);
return HttpStatusCode.Unauthorized;
};
Get["/signout"] = Post["/signout"] = parameters => {
var manager = Context.GetAuthenticationManager();
if (manager == null) {
throw new NotSupportedException("An OWIN authentication manager cannot be extracted from NancyContext");
}
// Instruct the cookies middleware to delete the local cookie created when the user agent
// is redirected from the identity provider after a successful authorization flow.
manager.SignOut("ClientCookie");
// Instruct the OpenID Connect middleware to redirect
// the user agent to the identity provider to sign out.
manager.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType);
return HttpStatusCode.OK;
};
}
}
}
Code source: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Nancy/Nancy.Client/Modules/AuthenticationModule.cs
Hope that helps!

Reconnecting to Servicestack session in an asp.net MVC4 application

I have an asp.net mvc4 web application that is consuming data data from an API written in C# and hosted on a Linux machine w/ Apache / mod_mono
The client application is written in C#/asp.net - It runs on a different web server, also Linux / Apache / mod_mono. I'm not sure if those details are important in this case, but I figured any background may help.
The question leading up to this one: AppHostBase instance not set - Helped me gain quite a bit more understanding of how this all fits together.
I believe the proper question I should be asking now is: Once I create a session in servicestack (On the API server), how do I properly reconnect to it?
Following the answers in previous questions, I've used this bit of code in my auth controller on the client application:
var authService = AppHostBase.Resolve<AuthService>();
authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var AuthResponse = authService.Authenticate(new Auth
{
provider = "credentials",
UserName = user.user_id,
Password = user.password,
RememberMe = true
});
This returns a ResolutionException:
Required dependency of type ServiceStack.ServiceInterface.Auth.AuthService could not be resolved.
Is there something simple I might be missing when it comes to getting the client to work from within an asp.net application?
I apologize if the question is too vague and will happily provide any more information.
Update:
This is AuthController - Excuse the mess, I've been trying a few things since my last post:
{
public partial class AuthController : BaseController
{
JsonServiceClient client = new ServiceStack.ServiceClient.Web.JsonServiceClient("<TheAPIurl>");
// GET: /Login/
public ActionResult login()
{
if (Session["IsAuthenticated"] != null)
{
ViewData["Result"] = Session["IsAuthenticated"];
}
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult login(UserModel user)
{
try
{
var authService = AppHostBase.Resolve<AuthService>();
authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var AuthResponse = authService.Authenticate(new Auth
{
provider = "credentials",
UserName = user.user_id,
Password = user.password,
RememberMe = true
});
if (AuthResponse.SessionId != null)
{
Session["IsAuthenticated"] = true;
Session["UserID"] = AuthResponse.UserName;
Session["jsclient"] = client;
FormsAuthentication.SetAuthCookie(user.user_id, true);
return Redirect("/default");
}
else
{
Session["IsAuthenticated"] = false;
}
}
catch (Exception ex)
{
Session["IsAuthenticated"] = false;
}
return View();
}
protected override void ExecuteCore()
{
throw new NotImplementedException();
}
}
}
Authenticating with a local ServiceStack instance
You can only retrieve an auto-wired ServiceStack service (or other IOC dependency) out from a ServiceStack Container if the ServiceStack instance is hosted within the same App Domain as MVC, i.e:
var authService = AppHostBase.Resolve<AuthService>();
authService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
Although the recommended code for resolving the auto-wired implementation of another service is:
using (var authAservice = AppHostBase.ResolveService<AuthService>()) {
...
}
i.e. As services may make use of resources that should be disposed. Inside a ServiceStack service you should use base.ResolveService<AuthService>() instead.
So if ServiceStack hosted within the same AppDomain as MVC, you can call the Service directory, like this:
var authResponse = authService.Authenticate(new Auth {
provider = "credentials",
UserName = user.user_id,
Password = user.password,
RememberMe = true
});
Authenticating with a Remote ServiceStack instance
Otherwise if it's remote you need to use one of ServiceStack's C# Service Clients, e.g:
var client = new JsonServiceClient(ServiceStackBaseUrl);
var authResponse = client.Post(new Auth {
provider = "credentials",
UserName = user.user_id,
Password = user.password,
RememberMe = true
});
Attaching ServiceStack SessionId back to originating MVC request
This will setup an authenticated session with that ServiceClient client, by attaching it to the ss-pid Cookie (see Session docs for more info). You can pass through this cookie to the originating browser that called MVC with:
var response = HttpContext.Current.Response.ToResponse();
response.Cookies.AddSessionCookie(
SessionFeature.PermanentSessionId, authResponse.SessionId);
Subsequent requests with the authenticated session
To re-attach with the remote authenticated ServiceStack Session from within MVC you would then need to pass the cookie back into the Service Client, e.g:
var cookie = HttpContext.Request.Cookies.Get(SessionFeature.PermanentSessionId);
var client = new JsonServiceClient(ServiceStackBaseUrl);
var cookie = new Cookie(SessionFeature.PermanentSessionId, cookie.Value);
client.CookieContainer.Add(cookie);
You can set the cookie domain, globally in the Web.Config:
<httpCookies domain="mydomain.com" />
Or at runtime with:
cookie.Domain = "mydomain.com";
The ServiceStack AuthTests.cs Integration Tests has some other useful examples showing how Authentication works in ServiceStack.

Servicestack user session not working

I have an API written in ServiceStack and I am attempting to build in authentication for clients. At the moment this API will only be accessed by Android clients (Xamarin / C#). The API itself is running on a Debian server with Apache / mod_mono
After reading up on Github, I am still not 100% sure how to put this together in such a way that... once the client has provided valid credentials (For testing, basic HTTP authentication) the user gets a session and the credentials are not checked again on subsequent requests from the same session.
AppHost Class:
{
public class AppHost
: AppHostBase
{
public AppHost() //Tell ServiceStack the name and where to find your web services
: base("Service Call Service", typeof(ServiceCallsService).Assembly) { }
public override void Configure(Funq.Container container)
{
//Set JSON web services to return idiomatic JSON camelCase properties
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Session storage
container.Register<ICacheClient>(new MemoryCacheClient());
// auth feature and session feature
Plugins.Add(new AuthFeature(
() => new AuthUserSession(),
new[] { new userAuth() }
) { HtmlRedirect = null } );
}
public class userAuth : BasicAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
peruseAuth peruseAuthHandler= new peruseAuth();
errorLogging MyErrorHandler = new errorLogging() ;
if (peruseAuthHandler.ValidateUser(authService, userName, password))
{
try
{
var session = (AuthUserSession)authService.GetSession(false);
session.UserAuthId = userName;
session.IsAuthenticated = true;
return true;
}
catch(Exception ex)
{
MyErrorHandler.LogError(ex, this) ;
return false ;
}
}
else
{
Console.Write("False");
return false;
}
}
}
The JsonServiceClient: (Just the "login" event)
btnLogin.Click += (sender, e) =>
{
// Set credentials from EditText elements in Main.axml
client.SetCredentials(txtUser.Text, txtPass.Text);
// Force the JsonServiceClient to always use the auth header
client.AlwaysSendBasicAuthHeader = true;
};
I've been doing a bit of logging, and it seems that every time the client performs an action their username/password is being checked against the database. Is there something wrong here, or is this the expected result?
For Basic Auth where the credentials are sent on each request it's expected.
In order for the ServiceClient to retain the Session cookies that have been authenticated you should set the RememberMe flag when you authenticate, e.g using CredentialsAuthProvider:
var client = new JsonServiceClient(BaseUrl);
var authResponse = client.Send(new Authenticate {
provider = "credentials",
UserName = "user",
Password = "p#55word",
RememberMe = true,
});
Behind the scenes this attaches the user session to the ss-pid cookie (permanent session cookie), which the client retains and resends on subsequent requests from that ServiceClient instance.

Categories