Implementing active authentication using ADFS - asp.net

I am working on the authentication with Active Directory using ADFS.
While searching, I got few articles to accomplish this requirement, but they are suggesting to redirect the Login page of application to Login page of ADFS and then come back.
Redirecting to ADFS Login page is not suggested as per user experience.
Can anyone help me to find out the solution to authenticate with active directory using ADFS behind the scene ? So, everything will be handled by application code, not by ADFS login page.
Please advise.
Please let me know if you have any concern or query or if you need more information.

The reason those articles suggest you redirect (using WS-Federation protocol) to the ADFS login page is because it allows you to set up federation to other identity providers (allow an external company' employees to use their own credentials to log in to your application).
What you want can be done using the WS-Trust protocol, but you'll give up (or have to implement yourself) the possibility to federate.
ADFS exposes endpoints like /adfs/services/trust/13/usernamemixed that you can talk to to get a security token. Something like below should get you going.
public class UserNameWSTrustBinding : WS2007HttpBinding
{
public UserNameWSTrustBinding()
{
Security.Mode = SecurityMode.TransportWithMessageCredential;
Security.Message.EstablishSecurityContext = false;
Security.Message.ClientCredentialType = MessageCredentialType.UserName;
}
}
private static SecurityToken GetSamlToken(string username, string password)
{
var factory = new WSTrustChannelFactory(new UserNameWSTrustBinding(), "https://yourdomain.com/adfs/services/trust/13/UsernameMixed")
{
TrustVersion = TrustVersion.WSTrust13
};
factory.Credentials.UserName.UserName = username;
factory.Credentials.UserName.Password = password;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference("https://yourdomain.com/yourservice"),
KeyType = KeyTypes.Bearer
};
var channel = factory.CreateChannel();
return channel.Issue(rst);
}

Related

Port over existing MVC user authentication to Azure functions

I have an old web application which is using ASP.net with the build in cookie based authentication which has the standard ASP.net SQL tables for storing the users credentials.
This is currently running as an Azure web app, but I was toying with the idea of trying to go serverless as per this example creating a ReactJs SPA hosting on blob storage to try and keep costs down and also improve performance without breaking the bank.
https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/serverless/web-app
I was wondering if it is possible to port over the existing ASP.net authentication to Azure functions, to instead return a JWT (JSON Web Token) which could be passed back in the headers to handle authenticated requests.
When I have tried this in the past I have failed misserably, so I was wondering if anyone knows if it is possible?
I've seen this article, which seems to talk about Azure functions doing authentication, but with Azure AD, which I don't think is right for what I need.
https://blogs.msdn.microsoft.com/stuartleeks/2018/02/19/azure-functions-and-app-service-authentication/
The answer is kind of. What I mean by this is that you can use your existing database and many of the same libraries, but you can't port over the code configuration. The default authentication for Functions is either 1) The default API tokens or 2) one of the EasyAuth providers baked into App Services which is in the guide you linked. Currently, any other solution you'll need to setup yourself.
Assuming you go with the JWT option, you'll need to turn off all of the built-in authentication for Functions. This includes setting your HttpRequest functions to AuthorizationLevel.Anonymous.
At a basic level You'll need to create two things. A function to issue tokens, and either a DI service or a custom input binding to check them.
Issuing tokens
The Functions 2.x+ runtime is on .NET Core so I'm gong to borrow some code from this blog post that describes using JWTs with Web API. It uses System.IdentityModel.Tokens.Jwt to generate a token, which we could then return from the Function.
public SecurityToken Authenticate(string username, string password)
{
//replace with your user validation
var user = _users.SingleOrDefault(x => x.Username == username && x.Password == password);
// return null if user not found
if (user == null)
return null;
// authentication successful so generate jwt token
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user.Id.ToString())
}),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
return tokenHandler.CreateToken(tokenDescriptor);
}
Validating Tokens
There are several guides out there for validating JWT within Azure Functions. I like this one from Ben Morris: https://www.ben-morris.com/custom-token-authentication-in-azure-functions-using-bindings/ (source code). It describes authenticating with either a custom input binding or with DI. Between the two, DI is the preferred option, unless there is a specific reason you need to use a binding. Here again, its the Microsoft.IdentityModel.JsonWebTokens and System.IdentityModel.Tokens.Jwt libraries that you'll need to do the bulk of the work.
public class ExampleHttpFunction
{
private readonly IAccessTokenProvider _tokenProvider;
public ExampleHttpFunction(IAccessTokenProvider tokenProvider)
{
_tokenProvider = tokenProvider;
}
[FunctionName("ExampleHttpFunction")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "example")] HttpRequest req, ILogger log)
{
var result = _tokenProvider.ValidateToken(req);
if (result.Status == AccessTokenStatus.Valid)
{
log.LogInformation($"Request received for {result.Principal.Identity.Name}.");
return new OkResult();
}
else
{
return new UnauthorizedResult();
}
}
}

Can I use WsFederationAuthentication and social login providers together?

I have an application with two areas: An admin area that uses ADFS authentication and a public area that uses social provider authentication (Google, Facebook, etc).
The ADFS and social login providers each work separately, but when I try to use both at the same time they step all over each other.
Here is my Startup.cs code:
public class Startup
{
public void Configuration(IAppBuilder app)
{
// I have tried setting this to DefaultAuthenticationTypes.ExternalCookie
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
// SECTION A: These lines work when SECTION B (below) is commented out.
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
UseGoogleAuthentication(app);
// SECTION B: These lines work when SECTION A (above) is commented out.
app.UseCookieAuthentication(new CookieAuthenticationOptions());
UseWsFederationAuthentication(app);
// I have tried switching SECTION A and SECTION B
}
private static void UseGoogleAuthentication(IAppBuilder app)
{
var googleClientId = ConfigurationManager.AppSettings["GoogleClientID"];
var googleClientSecret = ConfigurationManager.AppSettings["GoogleClientSecret"];
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
ClientId = googleClientId,
ClientSecret = googleClientSecret
});
}
private static void UseWsFederationAuthentication(IAppBuilder app)
{
var metaDataAddress = ConfigurationManager.AppSettings["ida:ADFSMetadata"];
var realm = ConfigurationManager.AppSettings["ida:Wtrealm"];
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = metaDataAddress,
Wtrealm = realm
});
}
}
Observed behavior
ADFS uncommented
Go to admin area
User logs into ADFS and is redirected back to admin area.
The .AspNet.Cookies cookie is present.
Context.GetOwinContext().Authentication.User has the ADFS claims.
Social uncommented
Go to public area
User logs into Google and is redirected back to public area.
The .AspNet.ExternalCookie is present.
I construct a ClaimsIdentity and store it in a FedAuth cookie.
Both uncommented
Admin area first, public area second
Go to admin area
User logs into ADFS and is redirected back to admin area.
The .AspNet.Cookies cookie is NOT present.
The .AspNet.ExternalCookies cookie IS present.
ADFS is sending back the correct claims but they are no longer present in Context.GetOwinContext().Authentication.User
Go to the public area
The .AspNet.ExternalCookies cookie is present with the SAME value.
Login in with Google.
The .AspNet.ExternalCookies cookie is present but with a DIFFERENT value.
Public area first, admin area second
Go to the public area
User logs into Google and is redirected back to public area.
The .AspNet.ExternalCookies cookie is present.
Go to the admin area
Login with ADFS.
The claims are sent back from ADFS but are no longer present in Context.GetOwinContext().Authentication.User.
Here is the final working code:
Ultimately, there needs to be two different cookies - one for ADFS and one for external providers. UseWsFederationAuthentication and UseExternalSignInCookie each presume things that cause problems for the other.
Specifically, app.UseExternalSignInCookie sets the default authentication type (see source below).
public static void UseExternalSignInCookie(this IAppBuilder app, string externalAuthenticationType)
{
if (app == null)
throw new ArgumentNullException("app");
app.SetDefaultSignInAsAuthenticationType(externalAuthenticationType);
IAppBuilder app1 = app;
CookieAuthenticationOptions authenticationOptions = new CookieAuthenticationOptions();
authenticationOptions.AuthenticationType = externalAuthenticationType;
authenticationOptions.AuthenticationMode = AuthenticationMode.Passive;
authenticationOptions.CookieName = ".AspNet." + externalAuthenticationType;
authenticationOptions.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
CookieAuthenticationOptions options = authenticationOptions;
app1.UseCookieAuthentication(options);
}
Notice it calls app.SetDefaultSignInAsAuthenticationType(externalAuthenticationType)
If you don't set WsFederationAuthenticationOptions.TokenValidationParameters.AuthenticationType, UseWsFederationAuthentication will use the default authentication type (i.e. the one set by app.UseExternalSignInCookie).
I'm guessing OWIN uses AuthenticationType as a key when creating, serializing, and deserializing authentication cookies. Since ADFS and external providers are using the same AuthenticationType (i.e. ExternalCookie), OWIN can't determine which one to use and overwrites the WsFederation cookie.
There are other minor details but this is the main one.

Single Sign On with Azure Active Directory

I have a bunch of web apps that I've created. Some of them use old-style webforms authentication, and some other sites at our company use different authentication patterns.
I'm trying to consolidate this, under one SSO pattern using Azure Active Directory. I've been trying to following guides/tutorials but it's not clicking for me.
My tech is currently ASP 4/MVC 5, although if ASP 5/MVC 6 is easier then I have the freedom to go that route as well. All web apps are hosted in Azure currently.
The confusion for me comes in that while looking through documentation, there seem to be so many ways to authentication and authorize users (also, authenticate vs authorize isn't absolutely clear to me).
I went to the Active Directory area of Azure Management portal (the old one). I added a new application called TestApp. I set the app URL to https://localhost:44320/, then sign-on URL to https://localhost:44320/, the tenant name to testapp. And my reply URL is https://localhost:44320/
This makes my app id uri https://localhost:44320/testapp I think? I also have my client ID guid.
The tutorial has an AccountController with a SignIn method like this:
public void SignIn()
{
// Send an OpenID Connect sign-in request.
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
When navigating to this, I receive the following in the browser:
[InvalidOperationException: IDX10803: Unable to create to obtain configuration from: 'https://localhost:44320/testapp/.well-known/openid-configuration'.]
I have a feeling it's because Azure is unable to redirect this all to back to my localhost? How can I set this up so I can test it out on Azure itself? And even further than that, will this solution be usable from multiple webapps? I'd assume they'd each be different applications in my Active Directory, but they'd all need to use a SSO procedure, where users can sign into multiple apps with one identity.
Sorry for any confusion, this is all very convoluted to me but I am trying to learn it as best I can.
Edit:
In the Startup of the webapp, I am calling this:
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
}
}
});
}
Which utilizes these app.config settings:
<add key="ida:ClientId" value="[my client id here]" />
<add key="ida:Tenant" value="testapp" />
<add key="ida:AADInstance" value="https://localhost:44320/{0}" />
<add key="ida:PostLogoutRedirectUri" value="https://localhost:44320/" />
Azure is able to redirect to localhost, it will just pop up a security confirmation asking if its ok to navigate to localhost.
Your tenant in app.config doesn't look right, change these app settings:
<add key="ida:Tenant" value="[YOUR TENANT].onmicrosoft.com" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}" />
To find out more about your tenant see this article: How to get an Azure Active Directory tenant
You can also try adding this code to your Notifications in Startup (just under AuthenticationFailed), try putting breakpoints on the handlers to see what happens:
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error?message=" + context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
return Task.FromResult(0);
}
Put an [Authorize] attribute on one of your controllers and it should redirect to the AAD authentication when you browse to it.
AFAIK each app would need a separate application in Azure AD and you would need to implement this Authentication in each separate app. I have managed to have a seamless sign in experience when I link via url from one app to another.
This answer sums up authentication vs authorization nicely: Authentication versus Authorization

Using Facebook access tokens obtained in mobile app to access ASP.Net WebAPI2 controller actions

Setup
Client: mobile app built on Cordova
Backend: ASP.net WebAPI2 (based on the standard template) configured with facebook login provider.
Problem
Having authenticated the user in the mobile app and received a Facebook access token, I pass this in subsequent requests as an HTTP header ("Authorization: Bearer "). This returns status 401 Unauthorized.
Questions
What am i missing here? How can i access the WebAPI controller actions based on the Facebook access token obtained on the mobile device?
On a high level, what i'm trying to achieve is this:
User opens mobile app and authenticates with Facebook
If user is not registered as a local user, he must choose a username to complete the registration
User is registered and can access API
I was facing the same problem and I found a really good solution here: http://codetrixstudio.com/mvc-web-api-facebook-sdk/
The WebApi web site can't understand the access token provided by Facebook. I guess it's because it hasn't been issued by itself (LOCAL AUTHORITY) but by an external provider. The approach explained in the link above is based on validating the token given by Facebook using it's API and recreating the access token.
So, you'll need some additional steps to achieve your goal.
The external providers have API so you can get information. For example, the https://graph.facebook.com/me?access_token={0} can be used to check if the token is valid. On the server side, you'll need to make a https web request to this URL passing the token (and the secret app as a proof, if the app is configured to ask it in Facebook).
Given the token is ok, you'll create an identity (ClaimsIdentity) using the information you've got at the API (id and username, for example). This identity will be needed to make an instance of the AuthenticationTicket class so you'll be able to issue a new access token for your Cordova app. Use this new bearer access token in the Authorization header of your https calls and your WebApi will recognized it as valid calls.
Ps. The good thing here is that you can set the token's expiration.
Since API's are stateless, there are multiple ways to secure it. In this case the mobile app has authenticated the user, but the API has not.
You can register the user's email and facebook ID into the database using a anonymous route. this can serve as both the login and register technically. (you could secure it with a clientid via OAuth if you don't want it fully open) along with thier current token. You verify the user against the facebook API on the server before registering of course just in case.
Create a custom route handler to secure the account controller or any other routes. The custom route handler would check the database for the current FB token and fb ID combo as well as token expire time, since you don't want to keep authenticating if it's expired.
Facebook has two types of tokens.
A Short lived token, initially created when you login and a long term token that lasts up to 60 days vs 1-2 hours.
for the api side, i suggest sticking to the short lived token and re-authenticate once expired but thats up to you.
You should grab a facebook SDK of your choice to verify and pull account info.
Example Secured Route:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "routename",
routeTemplate: "account",
constraints: null,
handler: new CustomFBHandler()
{
InnerHandler = new HttpControllerDispatcher(config)
},
defaults: new {controller = "default"}
);
}
Custom Handler: (note that you should pass any needed dependancies in the construtor)
public class CustomHandler : DelegatingHandler
{
public CustomHandler()
{
}
protected async Task<bool> IsAuthenticated(HttpRequestMessage requestMessage)
{
//Authenticate FB User Info HERE Against the Registered/logged in user....
}
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
bool isAuthenticated = false;
try
{
isAuthenticated = await IsAuthenticated(request);
}
catch (Exception e)
{
var response = request
.CreateResponse(HttpStatusCode.InternalServerError, new { status = new { code = 333, error = true, message = e.Message } }, new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
if (!isAuthenticated)
{
var response = request
.CreateResponse(HttpStatusCode.Unauthorized,new {status=new{code=1,error=true,message="Authorization Failed"}},new JsonMediaTypeFormatter());
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(Configuration.AuthenticationScheme));
return response;
}
return await base.SendAsync(request, cancellationToken);
}
}
You should send the FB Token and Facebook user id in the Headers. Once authenticated you can use the token/id to pull the user info you need from the database.

WCF RIA Services Form Authentication ServiceContext.User.Identity.Name Empty

We are using WCF Ria Services with silverlight 5 project.For authentication we are using Custom membership provider.WCF Ria Service in RIA Services class library.
Client side authentication running.We access current user name with WebContext.Current.User.Name.But server side ServiceContext.User empty.If we use [RequireAuthentication] attr. in DomainServices return always Access Denied.
How Can we push WebContext.Current.user to ServiceContext.user.I read several document and tutorial but every test fail.
Code examples :
CustomMembershipProvider.cs:
public class CustomMembershipProvider : MembershipProvider {
public override bool ValidateUser(string username, string password)
{
using (var context = new TimEntities())
{
var user = context.User.FirstOrDefault(u => u.Username == username &&
u.Password == password);
return user != null;
}
}
}
AuthenticationDomainService:
[EnableClientAccess]
public class AuthenticationDomainService : AuthenticationBase<AuthUser>
{}
public class AuthUser : UserBase
{}
App.Xaml.Cs:
var webContext = new WebContext();
var formsAuth = new FormsAuthentication();
var authContext = new AuthenticationDomainContext();
formsAuth.DomainContext = authContext;
webContext.Authentication = formsAuth;
ApplicationLifetimeObjects.Add(webContext);
I was having the same problem, but I have finally tracked down the answer. I had been messing about trying to expose SOAP endpoints, so I could access the same RIA services from a phone app. As part of that, I had added the following line in the App constructor:
WebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
Get rid of that, and you should have the user name available again. SOAP endpoints still seem to be exposed to the phone app as well.
At first you have to configure Forms authentication for your hosting website regardless whether you use WCF RIA Service or not. Moreover, Forms authentication have to be installed and enabled on IIS.
Then you have to configure ASP.NET membership in order to use your CustomMembershipProvider class.

Resources