There's a question about using SAML in ASP.Net Core, but I need additional help.
The only answer there mentions Kentor.AuthServices, but I don't understand how to use it. Everything I find on this or other SAML libraries, the documentation, blog posts, and sample applications are all about contacting some external authentication service and handling login and logout.
But I don't need any of that. The setup I'm working with does that in an edge-facing firewall application, and login/logout requests never reach my application. All I get is a SAML token in a cookie, which I need to validate and turn into a ClaimsPrincipal. I can't (the deployment network setup is insanely paranoid) and don't want to contact any identity provider.
Currently I've written a piece of middleware that takes the cookie, parses it, and parses out the parts I need for the claims principal. But I don't do any validation, either of the XML signature or of the SAML validity (valid time attributes etc). With .Net Core 2.0 Preview 2 I can do the XML signature validation, but I'm still stuck on doing the SAML validation. Is there a library that simply validates SAML constraints and does nothing else (or, at least, where I can ignore everything else)? I believe Kentor or ITfoxtec or elerch's SAML2.Core must contain such functionality, but I can't figure out where it is.
I have done this with SecurityTokenHandlerCollection class in System.IdentityModel.Tokens
I hope this code will help you.
public Saml2SecurityToken DeserializeSAMLResponse(string samlResponse)
{
//Deserializing saml response
Saml2SecurityToken token;
using (var reader = XmlReader.Create(new StringReader(samlResponse)))
{
reader.ReadToFollowing("Assertion", Infrastructure.Enumerations.StringEnum.GetStringValue(SAMLProtocoles.SAML_20_ASSERTION));
// Deserialize the token so that data can be taken from it and plugged into the RSTR
SecurityTokenHandlerCollection tokenHandlerCollection = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
token = (Saml2SecurityToken)tokenHandlerCollection.ReadToken(reader.ReadSubtree());
}
//Deserializing successful
return token;
}
It will internally validate the SAML and parse it in Saml2SecurityToken
After you get the token you can the the Users Credentials like this
public User ReadSamlResponse(string samlResponse, string profileName, bool isSAMLProfile = true)
{
User User = new User();
var DecodedSamlResponse = Convert.FromBase64String(samlResponse);
string ResponseDecoded = coding.UTF8.GetString(DecodedSamlResponse);
Saml2SecurityToken Token = _samlAuthenticationService.DeserializeSAMLResponse(ResponseDecoded);
if ()// apply condition here if you need to validate signature
{
if (!_samlAuthenticationService.ValidateSamlToken(ResponseDecoded, AuthenticationConnector, isSAMLProfile))
throw new Exception("Signature is invalid");
}
User = GetUserFromToken(Token);
return User;
}
And to get User for Security Token you can do this
public User GetUserFromToken(Saml2SecurityToken Token)
{
//Get user information from the token started
User User = new User();
if (Token != null)
{
if (Token.Assertion.Subject.NameId != null && (Token.Assertion.Subject.NameId.Format == null || Token.Assertion.Subject.NameId.Format.OriginalString == "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"))
User.EmailAddress = Token.Assertion.Subject.NameId.Value;
foreach (var Statement in Token.Assertion.Statements)
{
var AttributeStatement = Statement as Saml2AttributeStatement;
var AuthenticationStatement = Statement as Saml2AuthenticationStatement;
if (AttributeStatement != null)
foreach (var Saml2Attribute in AttributeStatement.Attributes)
{
if (Saml2Attribute.Name.Equals("mail") || Saml2Attribute.Name.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"))
User.EmailAddress = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("uid") || Saml2Attribute.Name.Equals("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"))
User.Name = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("phone"))
User.MobileNumber = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("title"))
User.JobTitle = Saml2Attribute.Values[0];
if (Saml2Attribute.Name.Equals("company"))
User.CompanyName = Saml2Attribute.Values[0];
}
if (AuthenticationStatement != null)
{
User.SAMLSessionIndex = AuthenticationStatement.SessionIndex;
}
}
}
//Successfully parsed user credentials
return User;
}
http://blog.scottlogic.com/2015/11/19/oauth2-with-saml2.html
This blog of scott has explained it in simple way.
Related
I am trying to get a CUSTOM_AUTH flow with AWS Cognito in the following setup:
Angular TS client app
.NET 3.1 WebAPI
Login is handled serverside, UserName + Password check works correctly. Business requires to add email based MFA.
I struggle to understand how to implement CUSTOM_AUTH flow from Server-side. All samples and documentation are restricted to Client flow, which includes SRP values.
The desired flow would be:
User enters username + password
API send them to Cognito, which triggers the CUSTOM AUTH Flow
Custom flow triggers, code being generated and an email sent out to the user email address with a generated code
Client app renders an input field for the code generated
user submits the code received via email
API calls Cognito with the generated code
Cognito confirms if the provided code match the generated one and generates the AuthToken to be used
The current login flow looks as:
user inputs username and password in the Angular App
inputs are sent to the backend API
Backend Initiates the authentication flow with Cognito through SDK
public async Task<AdminInitiateAuthResponse> AuthenticateUser(string username, string password) {
var authRequest = new AdminInitiateAuthRequest
{
UserPoolId = this._poolId,
ClientId = this._clientId,
AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH
};
authRequest.AuthParameters.Add("USERNAME", username);
authRequest.AuthParameters.Add("PASSWORD", password);
return await this._client.AdminInitiateAuthAsync(authRequest);
}
Cognito validates the username and password. From the return value we generate a token.
public CognitoUserDto CreateUserToken(AuthenticationResultType authRes) {
var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler();
var awsIdToken = tokenHandler.ReadJwtToken(authRes.IdToken);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = creds
};
return new CognitoUserDto
{
Email = awsIdToken.Claims.Single(e => e.Type == "email").Value,
Token = CreateToken(awsIdToken, authRes.AccessToken)
};
}
public string CreateToken(JwtSecurityToken awsToken, string accessToken) {
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Email, awsToken.Claims.Single(e => e.Type == "email").Value),
new Claim("accessToken", accessToken)
};
var creds = new SigningCredentials(_key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = creds
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
As for Custom Auth mode with email the sample here was followed (even though its an app flow). Since I failed to find samples/documentation with server side custom auth flow attempts are pretty much restricted to trial and error.
If possible I would like to avoid the need to refactor the whole process to client side flow.
What has been done so far:
Replaced AuthFlow in AuthenticateUser method to AuthFlowType.CUSTOM_AUTH
User Pool signin experience is set to Optional MFA
Created the lambda triggers in AWS Cognito
Issues faced:
keeping the ADMIN_NO_SRP_AUTH mode does not trigger the CUSTOM AUTH flow. (somewhat expected)
changing the AuthFlow to CUSTOM_AUTH throws an exception. First sentence in AWS Docs state here states "The request for this Lambda trigger contains session", yet the Lambda expression stops with an exception that session is null. Yet, this may be right, since according to step 1 one shall include value for SRP_A, which is kinda obscure how, given no SRP is available.
reworked the defaultAuthChallenge Lambda expression to handle if session is null. In this case Lambda was executed without exception, however challengeName and session are not returned, so kinda blocked how to call AdminRespondToAuthChallengeAsync() method
public async Task<AdminRespondToAuthChallengeResponse> RespondToCognitoChallenge(string token, string session) {
return await this._client.AdminRespondToAuthChallengeAsync(new AdminRespondToAuthChallengeRequest() {
ChallengeName = ChallengeNameType.CUSTOM_CHALLENGE,
CientId = this._clientId,
UserPoolId = this._poolId,
ChallengeResponses = new Dicitonary<string, string> {{ "token", token }},
Session = session
});
}
The reworked defaultAuthChallenge Lambda look like:
export const handler = async (event) => {
if (event.request?.session === null || event.request.session.length === 0) {
//SRP_A is the first challenge, this will be implemented by cognito. Set next challenge as PASSWORD_VERIFIER.
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'PASSWORD_VERIFIER';
} else if (event.request.session && event.request.session.length === 1
&& event.request.session[1].challengeName === 'PASSWORD_VERIFIER'
&& event.request.session[1].challengeResult === true) {
//If password verification is successful then set next challenge as CUSTOM_CHALLENGE.
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
} else if (event.request.session && event.request.session.length >= 5
&& event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE'
&& event.request.session.slice(-1)[0].challengeResult === false) {
//The user has exhausted 3 attempts to enter correct otp.
event.response.issueTokens = false;
event.response.failAuthentication = true;
} else if (event.request.session && event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE'
&& event.request.session.slice(-1)[0].challengeResult === true) {
//User entered the correct OTP. Issue tokens.
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
//user did not provide a correct answer yet.
event.response.issueTokens = false;
event.response.failAuthentication = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
return event;
};
Your kind help is apprecaited!
I'm trying to build an API (using ASP.NET WebApi) that will be consumed by a native mobile app for a school project. (I'm not concerned about/developing the mobile app, this responsibility falls on a different member)
I'm at a point where I need to implement a token based Facebook login. There are a lot of tutorials available for how to implement this feature for browser based apps (this is pretty straight forward and most of it comes inbuilt), but I don't think I follow how this would work with native apps. What I don't understand is how the redirects would work?
According to this link, nothing needs to be handled specifically by my server. And I don't think I understand how this would work? How would the tokens from Facebook be handled?
Also, what part of token handling should I implement, I couldn't really find good documentation for WebApi external login authentication.
Anyway, if someone could point me to the exact flow of token exchanges that happen and what is implemented by default by ASP.NET, that would be super helpful.
Also, the biggest point of confusion for me is I don't understand how the token returned by Facebook will be handled.
I assume the token will be returned to the client (mobile app), how do I get access to it on my server?
How do I create a local token from facebook's token?
Is this all done internally/auto-magically by ASP.NET?
I'm sorry if this is something I should've been able to figure out. I did do quite a bit of research and I found myself drowning in (related & unrelated) information. I don't think I even know how to search for the information I need.
Some links I've read:
Claims And Token Based Authentication (ASP.NET Web API)
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app
I had to do pretty much the same thing for an application I was working on. I also had a lot of trouble finding information about it. It seemed like everything I found was close to what I needed, but not exactly the solution. I ended up taking bits and pieces from a bunch of different blog posts, articles, etc. and putting them all together to get it to work.
I remember two of the links you posted "Claims and Token Based Authentication" and "ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app" as being ones that had useful information.
I can't give you a comprehensive answer since I don't remember everything I had to do, nor did I even understand everything I was doing at the time, but I can give you the general idea. You are on the right track.
Essentially I ended up using the token granted by Facebook to confirm that they were logged into their Facebook account, created a user based on their Facebook user ID, and granted them my own bearer token that they could use to access my API.
The flow looks something like this:
Client authenticates with Facebook via whatever method (we used oauth.io)
Facebook returns them a token
Client sends token information to the registration endpoint of my WebApi controller
The token is validated using Facebook's Graph API, which returns user info
A user is created in the database via ASP.NET Identity with their Facebook user ID as the key
Client sends token information to the authentication endpoint of my WebApi controller
The token is validated using Facebook's Graph API, which returns user info
The user info is used to look up the user in the database, confirm they have previously registered
ASP.NET Identity is used to generate a new token for that user
That token is returned to the client
Client includes an Authorization header in all future HTTP requests with the new token granted by my service (ex. "Authorization: Bearer TOKEN")
If the WebApi endpoint has the [Authorize] attribute, ASP.NET Identity will automatically validate the bearer token and refuse access if it is not valid
There ended up being a lot of custom code for implementing the OAuth stuff with ASP.NET Identity, and those links you included show you some of that. Hopefully this information will help you a little bit, sorry I couldn't help more.
I followed this article. The flow is basically this
The server has the facebook keys just like with web login
The app asks for available social logins and displays buttons (you can hardcode this I guess)
When a button is pressed the app opens a browser and sets the URL to the one related to the specified social login. The ASP.NET then redirects the browser to facebook/google/whatever with the appropriate Challenge
The user might be logged in or not and might have given permission to your app or not. After he gives the permissions facebook redirects back to the provided callback URL
At that point you can get the external login info from the SignInManager and check if the user already exists and if you should create a new account
Finally a token is generated and the browser is redirected to a URL in which the token is placed. The app gets the token from the URL and closes the browser. Uses the token to proceed with API requests.
Honestly I have no idea if this approach is legit...
The code of the action buttons should redirect to:
public async Task<IEnumerable<ExternalLoginDto>> GetExternalLogins(string returnUrl, bool generateState = false)
{
IEnumerable<AuthenticationScheme> loginProviders = await SignInManager.GetExternalAuthenticationSchemesAsync();
var logins = new List<ExternalLoginDto>();
string state;
if (generateState)
{
const int strengthInBits = 256;
state = RandomOAuthStateGenerator.Generate(strengthInBits);
}
else
{
state = null;
}
foreach (AuthenticationScheme authenticationScheme in loginProviders)
{
var routeValues = new
{
provider = authenticationScheme.Name,
response_type = "token",
client_id = Configuration["Jwt:Issuer"],
redirect_uri = $"{Request.Scheme}//{Request.Host}{returnUrl}",
state = state
};
var login = new ExternalLoginDto
{
Name = authenticationScheme.DisplayName,
Url = Url.RouteUrl("ExternalLogin", routeValues),
State = state
};
logins.Add(login);
}
return logins;
}
The code for the callback action:
[Authorize(AuthenticationSchemes = "Identity.External")]
[Route("ExternalLogin", Name = "ExternalLogin")]
public async Task<IActionResult> GetExternalLogin(string provider, string state = null, string client_id = null, string error = null)
{
if (error != null)
{
ThrowBadRequest(error);
}
if (!User.Identity.IsAuthenticated)
{
return new ChallengeResult(provider);
}
string providerKey = User.FindFirstValue(ClaimTypes.NameIdentifier);
var externalLoginInfo = new ExternalLoginInfo(User, User.Identity.AuthenticationType, providerKey, User.Identity.AuthenticationType);
if (externalLoginInfo.LoginProvider != provider)
{
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
return new ChallengeResult(provider);
}
var userLoginInfo = new UserLoginInfo(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey, externalLoginInfo.ProviderDisplayName);
User user = await UserManager.FindByLoginAsync(externalLoginInfo.LoginProvider, externalLoginInfo.ProviderKey);
if (client_id != Configuration["Jwt:Issuer"])
{
return Redirect($"/#error=invalid_client_id_{client_id}");
}
if (user != null)
{
return await LoginWithLocalUser(user, state);
}
else
{
string email = null;
string firstName = null;
string lastName = null;
IEnumerable<Claim> claims = externalLoginInfo.Principal.Claims;
if (externalLoginInfo.LoginProvider == "Google")
{
email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
firstName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
lastName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;
}
else if (externalLoginInfo.LoginProvider == "Facebook")
{
email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value;
string[] nameParts = claims.First(c => c.Type == ClaimTypes.Name)?.Value.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
firstName = nameParts?.First();
lastName = nameParts?.Last();
}
//some fallback just in case
firstName ??= externalLoginInfo.Principal.Identity.Name;
lastName ??= externalLoginInfo.Principal.Identity.Name;
user = new User
{
UserName = email,
Email = email,
FirstName = firstName,
LastName = lastName,
EmailConfirmed = true //if the user logs in with Facebook consider the e-mail confirmed
};
IdentityResult userCreationResult = await UserManager.CreateAsync(user);
if (userCreationResult.Succeeded)
{
userCreationResult = await UserManager.AddLoginAsync(user, userLoginInfo);
if (userCreationResult.Succeeded)
{
return await LoginWithLocalUser(user, state);
}
}
string identityErrrors = String.Join(" ", userCreationResult.Errors.Select(ie => ie.Description));
Logger.LogWarning($"Error registering user with external login. Email:{email}, Errors:" + Environment.NewLine + identityErrrors);
return Redirect($"/#error={identityErrrors}");
}
}
private async Task<RedirectResult> LoginWithLocalUser(User user, string state)
{
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
DateTime expirationDate = DateTime.UtcNow.AddDays(365);
string token = user.GenerateJwtToken(Configuration["Jwt:Key"], Configuration["Jwt:Issuer"], expirationDate);
return Redirect($"/#access_token={token}&token_type=bearer&expires_in={(int)(expirationDate - DateTime.UtcNow).TotalSeconds}&state={state}");
}
I was tasked with adding logging via external service (using SAML 2.0) to an MVC app (.Net 4.5) that uses SimpleMembership. To be honest I'm not even sure where to start. From what I found on the internet there are few points to the problem. Most of the materials I found dealt with communication with the SAML identity provider (frequently written from scratch). However before I can reach that point I need to make sure I can actually integrate it with the SimpleMembership which we are using.
I suspect for starters I would need something like SAMLWebSecurity (akin to OAuthWebSecurity which we also use). I have found no such thing* on the internet which makes me believe it does not exist (though I wouldn't mind being wrong here). This makes me believe I would have to write it myself, but can I do that without have to write my own membership provider?
*I'm not sure what would be a correct way to call this static class.
I'd recommend that you upgrade to ASP.NET Identity and the OWIN Based authentication middleware. Then you can use Kentor.AuthServices middleware that works with ASP.NET Identity (except that the XSRF-guard has to be commented out until bug #127 has been resolved).
You could also use the SAML classes from Kentor.AuthServices if you have to stick with SimpleMembership, so that you don't have to implement SAML from scratch.
Disclaimer: I'm the author of Kentor.AuthServices, but since it's open source, I'm not making money on people using it.
After discussing it with a colleague I think I figured out the course of actions. Both OAuthWebSecurity and WebSecurity appear to be a part of SimpleMembership, so what I wrote in the question would indicate I want to write a custom membership or reverse engineer SimpleMembership to copy OAuthWebSecurity (which doesn't sound like a fun activity to have).
My best bet here is hijacking the OAuthWebSecurity, by writing a custom client (one which implements the IAuthenticationClient interface). Normally one registers various OAuth clients using OAuthWebSecurity's built in methods (like RegisterFacebookClient). But it is also possible to register those clients using OAuthWebSecurity.RegisterClient which accepts IAuthenticationClient. This way I should be able to add this SAML login without writing a custom membership provider and keep using SimpleMembership.
I managed to do this. Thankfully the identity provider wasn't extremely complicated so all I had to do was redirect to a certain address (I didn't even need to request assertion). After a successful login, the IDP "redirects" the user using POST to my site with the base64 encoded SAMLResponse attached. So all I had to do was to parse and validate the response. I placed the code for this in my custom client (implementing IAuthenticationClient interface).
public class mySAMLClient : IAuthenticationClient
{
// I store the IDP certificate in App_Data
// This can by actually skipped. See VerifyAuthentication for more details
private static X509Certificate2 certificate = null;
private X509Certificate2 Certificate
{
get
{
if (certificate == null)
{
certificate = new X509Certificate2(Path.Combine(HttpContext.Current.ApplicationInstance.Server.MapPath("~/App_Data"), "idp.cer"));
}
return certificate;
}
}
private string providerName;
public string ProviderName
{
get
{
return providerName;
}
}
public mySAMLClient()
{
// This probably should be provided as a parameter for the constructor, but in my case this is enough
providerName = "mySAML";
}
public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
{
// Normally you would need to request assertion here, but in my case redirecting to certain address was enough
context.Response.Redirect("IDP login address");
}
public AuthenticationResult VerifyAuthentication(HttpContextBase context)
{
// For one reason or another I had to redirect my SAML callback (POST) to my OAUTH callback (GET)
// Since I needed to retain the POST data, I temporarily copied it to session
var response = context.Session["SAMLResponse"].ToString();
context.Session.Remove("SAMLResponse");
if (response == null)
{
throw new Exception("Missing SAML response!");
}
// Decode the response
response = Encoding.UTF8.GetString(Convert.FromBase64String(response));
// Parse the response
var assertion = new XmlDocument { PreserveWhitespace = true };
assertion.LoadXml(response);
//Validating signature based on: http://stackoverflow.com/a/6139044
// adding namespaces
var ns = new XmlNamespaceManager(assertion.NameTable);
ns.AddNamespace("samlp", #"urn:oasis:names:tc:SAML:2.0:protocol");
ns.AddNamespace("saml", #"urn:oasis:names:tc:SAML:2.0:assertion");
ns.AddNamespace("ds", #"http://www.w3.org/2000/09/xmldsig#");
// extracting necessary nodes
var responseNode = assertion.SelectSingleNode("/samlp:Response", ns);
var assertionNode = responseNode.SelectSingleNode("saml:Assertion", ns);
var signNode = responseNode.SelectSingleNode("ds:Signature", ns);
// loading the signature node
var signedXml = new SignedXml(assertion.DocumentElement);
signedXml.LoadXml(signNode as XmlElement);
// You can extract the certificate from the response, but then you would have to check if the issuer is correct
// Here we only check if the signature is valid. Since I have a copy of the certificate, I know who the issuer is
// So if the signature is valid I then it was sent from the right place (probably).
//var certificateNode = signNode.SelectSingleNode(".//ds:X509Certificate", ns);
//var Certificate = new X509Certificate2(System.Text.Encoding.UTF8.GetBytes(certificateNode.InnerText));
// checking signature
bool isSigned = signedXml.CheckSignature(Certificate, true);
if (!isSigned)
{
throw new Exception("Certificate and signature mismatch!");
}
// If you extracted the signature, you would check the issuer here
// Here is the validation of the response
// Some of this might be unnecessary in your case, or might not be enough (especially if you plan to use SAML for more than just SSO)
var statusNode = responseNode.SelectSingleNode("samlp:Status/samlp:StatusCode", ns);
if (statusNode.Attributes["Value"].Value != "urn:oasis:names:tc:SAML:2.0:status:Success")
{
throw new Exception("Incorrect status code!");
}
var conditionsNode = assertionNode.SelectSingleNode("saml:Conditions", ns);
var audienceNode = conditionsNode.SelectSingleNode("//saml:Audience", ns);
if (audienceNode.InnerText != "Name of your app on the IDP")
{
throw new Exception("Incorrect audience!");
}
var startDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotBefore"].Value, XmlDateTimeSerializationMode.Utc);
var endDate = XmlConvert.ToDateTime(conditionsNode.Attributes["NotOnOrAfter"].Value, XmlDateTimeSerializationMode.Utc);
if (DateTime.UtcNow < startDate || DateTime.UtcNow > endDate)
{
throw new Exception("Conditions are not met!");
}
var fields = new Dictionary<string, string>();
var userId = assertionNode.SelectSingleNode("//saml:NameID", ns).InnerText;
var userName = assertionNode.SelectSingleNode("//saml:Attribute[#Name=\"urn:oid:1.2.840.113549.1.9.1\"]/saml:AttributeValue", ns).InnerText;
// you can also extract some of the other fields in similar fashion
var result = new AuthenticationResult(true, ProviderName, userId, userName, fields);
return result;
}
}
Then I just registered my client in App_Start\AuthConfig.cs using OAuthWebSecurity.RegisterClient and then I could reuse my existing external login code (which was originally made for OAUTH). For various reasons my SAML callback was a different action than my OAUTH callback. The code for this action was more or less this:
[AllowAnonymous]
public ActionResult Saml(string returnUrl)
{
Session["SAMLResponse"] = Request.Form["SAMLResponse"];
return Redirect(Url.Action("ExternalLoginCallback") + "?__provider__=mySAML");
}
Additionally OAuthWebSecurity.VerifyAuthentication didn't work with my client too well, so I had to conditionally run my own verification in the OAUTH callback.
AuthenticationResult result = null;
if (Request.QueryString["__provider__"] == "mySAML")
{
result = new mySAMLClient().VerifyAuthentication(HttpContext);
}
else
{
// use OAuthWebSecurity.VerifyAuthentication
}
This probably all looks very weird and might differ greatly in case of your IDP, but thanks to this I was able to reuse most of the existing code for handling external accounts.
I have inherited an existing application. This application uses ASP.NET MVC 3. It has some APIs. Those APIs look like the following:
[AcceptVerbs(HttpVerbs.Post)]
[Endpoint]
public ActionResult AuthenticatePlayer(string username, string password)
{
// Ensure that the user entered valid credentials
if (Membership.ValidateUser(username, password) == false)
return Json(new { statusCode = StatusCodes.INVALID_CREDENTIALS, message = "You entered an invalid username or password. Please try again." });
// Get the profile of the person that just logged in.
ProfileCommon userProfile = (ProfileCommon)(ProfileCommon.Create(username));
if (userProfile != null)
{
string name = username;
if (String.IsNullOrEmpty(userProfile.FirstName) == false)
name = userProfile.FirstName;
return Json(new {
statusCode = StatusCodes.SUCCESS,
payload = name,
username = username.ToLower(),
});
}
}
[AcceptVerbs(HttpVerbs.Get)]
[Endpoint]
public ActionResult SomeUserAction(string q)
{
// TODO: Ensure the user is authorized to perform this action via a token
// Do something
return Json(new { original = q, response = DateTime.UtcNow.Millisecond }, JsonRequestBehavior.AllowGet);
}
I'm trying to figure out how to integrate a token-based authorization schema into this process. From my understanding, a token-based system would return a short-lived token and a refresh token to a user if they successfully login. Then, each method can check to see if a user is authorized to perform the action by looking at the token. I'm trying to learn if this is built-in to ASP.NET MVC or if there is a library I can use. I need to figure out the shortest way to get this done.
Thank you so much!
I've built a WebAPI Token Authentication library a year ago, providing Token based authentication:
WebAPI Token Auth Bootstrap is out of the box Token based User Auth for WebAPI applications, Provides ready to use 'TokenAuthorize'
Attribute and 'TokenAuthApiController' Controller.
Among its features - Token Based User Authentication User Property inside the
TokenAuthApiController (Id, Username, Role, LastAccess).
Token Based User Authorization TokenAuthorizeAttribute with Access
Level - Public, User, Admin or Anonymous.
Built-in Functionality Login(), Logoff(), Error(), Unauthorized()
Responses with various overloads.
You can read more about here and in its own wiki in GitHub.
Nowadays I am working on a Node.js application and I am using Json Web Tokens (JWT) using Node.js library and it is very easy and straightforward.. its Node.js after all ;)
I saw there is a .NET implementation of JWT explained on this article which I recommend you to look at.
You can use Owin ... i.e. Microsoft.owin.security
I haven't tried this implementation but this is just to give you an idea:
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Json(new {
statusCode = StatusCodes.SUCCESS,
payload = name,
username = username.ToLower(),
accessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket)
});
I have a Asp.Net application that will post tweets in twitter.
I'm using Twitterizer2 for doing this.
First time when the user uses the application, he will be redirected to twitter for authentication.
And then the user-token will be stored in my application , so that the user will never be asked again to login to twitter.
This is working fine.
Now i want to validate the user-tokens before posting (ie valid token or not) . Is there any way to do this validation?
You can make a call to the Verify Credentials API
Make an authenticated call to
https://api.twitter.com/1/account/verify_credentials.json
It will respond with HTTP 200 OK if the tokens are correct - or 401 if they are not.
MoH's code didn't work for me. Here's what I did:
public bool IsTwitterAccessTokenValid(String access_token, String token_secret)
{
var token = new Twitterizer.OAuthTokens();
token.ConsumerKey = this.TwitterConsumerKey;
token.ConsumerSecret = this.TwitterConsumerSecret;
token.AccessToken = access_token;
token.AccessTokenSecret = token_secret;
var twitterResponse = TwitterAccount.VerifyCredentials(token);
return (twitterResponse.Result == RequestResult.Success);
}
I found out the code for validating tokens in another question.
The Twitterizer Api itself had the Methods to validate the User tokens.
The code is as follows:
Twitterizer.OAuthTokens token = new Twitterizer.OAuthTokens();
token.ConsumerKey = this.AppId;
token.ConsumerSecret = this.AppSecret;
token.AccessToken = userToken;
token.AccessTokenSecret = userSecret;
Twitterizer.TwitterResponse<Twitterizer.TwitterUser> response =
Twitterizer.TwitterAccount.VerifyCredentials(token);
if (String.IsNullOrEmpty(response.ErrorMessage))
{
//This is a valid token
}
else
{
//Invalid token
}