Question: I have a document management system, and I am building a Web-Service interfaces to the database.
Everything works so far, just that right now, it's totally unsecured, everybody can access it.
How can I incorporate password or private-public key authentication ?
I can only find 'best practises' and using 'windows user' or passport authentication.
But I need authentication from a user and password stored in the database, or better for an RSA private-key stored for each web-service user in the database...
Edit:
I have to use the .NET Framework 2.0 in an ASP.NET environment
The solution is to write an own http module with a mixture of code provided by MSDN and CodeProject. Including own fixes of MS bugs, and then add this custom soap header to the web service.
<SoapHeader("Authentication", Required:=True)>
This is the module:
Imports System.Web
Imports System.Web.Services.Protocols
' http://msdn.microsoft.com/en-us/library/9z52by6a.aspx
' http://msdn.microsoft.com/en-us/library/9z52by6a(VS.80).aspx
' http://www.codeproject.com/KB/cpp/authforwebservices.aspx
' http://aleemkhan.wordpress.com/2007/09/18/using-wse-30-for-web-service-authentication/
' http://www.codeproject.com/KB/WCF/CustomUserNamePassAuth2.aspx
' http://www.codeproject.com/KB/WCF/CustomUserNamePassAuth2.aspx
' http://www.codeproject.com/KB/webservices/WS-Security.aspx
'Public NotInheritable Class WebServiceAuthenticationModule
Public Class WebServiceAuthenticationModule
Implements System.Web.IHttpModule
Protected Delegate Sub WebServiceAuthenticationEventHandler(ByVal sender As [Object], ByVal e As WebServiceAuthenticationEvent)
Protected _eventHandler As WebServiceAuthenticationEventHandler = Nothing
Protected Custom Event Authenticate As WebServiceAuthenticationEventHandler
AddHandler(ByVal value As WebServiceAuthenticationEventHandler)
_eventHandler = value
End AddHandler
RemoveHandler(ByVal value As WebServiceAuthenticationEventHandler)
_eventHandler = value
End RemoveHandler
RaiseEvent(ByVal sender As Object,
ByVal e As WebServiceAuthenticationEvent)
End RaiseEvent
End Event
Protected app As HttpApplication
Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init
app = context
context.Context.Response.Write("<h1>Test</h1>")
AddHandler app.AuthenticateRequest, AddressOf Me.OnEnter
End Sub
Public Sub Dispose() Implements System.Web.IHttpModule.Dispose
' add clean-up code here if required
End Sub
Protected Sub OnAuthenticate(ByVal e As WebServiceAuthenticationEvent)
If _eventHandler Is Nothing Then
Return
End If
_eventHandler(Me, e)
If Not (e.User Is Nothing) Then
e.Context.User = e.Principal
End If
End Sub 'OnAuthenticate
Public ReadOnly Property ModuleName() As String
Get
Return "WebServiceAuthentication"
End Get
End Property
Sub OnEnter(ByVal [source] As [Object], ByVal eventArgs As EventArgs)
'Dim app As HttpApplication = CType([source], HttpApplication)
'app = CType([source], HttpApplication)
Dim context As HttpContext = app.Context
Dim HttpStream As System.IO.Stream = context.Request.InputStream
' Save the current position of stream.
Dim posStream As Long = HttpStream.Position
' If the request contains an HTTP_SOAPACTION
' header, look at this message.
'For Each str As String In context.Request.ServerVariables.AllKeys
'If context.Request.ServerVariables(Str) IsNot Nothing Then
'context.Response.Write("<h1>" + Str() + "= " + context.Request.ServerVariables(Str) + "</h1>")
'End If
'Next
If context.Request.ServerVariables("HTTP_SOAPACTION") Is Nothing Then
'context.Response.End()
Return
'Else
'MsgBox(New System.IO.StreamReader(context.Request.InputStream).ReadToEnd())
End If
' Load the body of the HTTP message
' into an XML document.
Dim dom As New System.Xml.XmlDocument()
Dim soapUser As String
Dim soapPassword As String
Try
dom.Load(HttpStream)
'dom.Save("C:\Users\Administrator\Desktop\SoapRequest.xml")
' Reset the stream position.
HttpStream.Position = posStream
' Bind to the Authentication header.
soapUser = dom.GetElementsByTagName("Username").Item(0).InnerText
soapPassword = dom.GetElementsByTagName("Password").Item(0).InnerText
Catch e As Exception
' Reset the position of stream.
HttpStream.Position = posStream
' Throw a SOAP exception.
Dim name As New System.Xml.XmlQualifiedName("Load")
Dim ssoapException As New SoapException("Unable to read SOAP request", name, e)
context.Response.StatusCode = System.Net.HttpStatusCode.Unauthorized
context.Response.StatusDescription = "Access denied."
' context.Response.Write(ssoapException.ToString())
'Dim x As New System.Xml.Serialization.XmlSerializer(GetType(SoapException))
'context.Response.ContentType = "text/xml"
'x.Serialize(context.Response.OutputStream, ssoapException)
'Throw ssoapException
context.Response.End()
End Try
' Raise the custom global.asax event.
OnAuthenticate(New WebServiceAuthenticationEvent(context, soapUser, soapPassword))
Return
End Sub 'OnEnter
End Class ' WebServiceAuthenticationModule
If you are still using an ASP.NET SOAP web service then the easiest way that fits your requirements IMO is to use the ASP.NET Forms authentication with a Membership DB. If you are starting out fresh I'd recommend going with WCF - if you can't/or won't do that this post applies to the "classic" ASP.NET SOAP web services.
To add Forms authentication to a web service:
Configure it just like you would for any other web site but set it to allow access for everyone:
<authorization>
<allow users="*"/>
</authorization>
Implement Login/Logout methods and issue the authentication ticket in the Login method. Further requests to the web service then can use the issued authentication ticket.
All other web methods you want to protect you can then decorate with
[PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
These methods will now throw a Security exception if a client is not authenticated.
Example for a protected method:
[PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
[WebMethod(Description = "Your protected method")]
public string Foo()
{
return "bar";
}
Example for Login method:
[WebMethod(Description = "Login to start a session")]
public bool Login(string userName, string password)
{
if (!Membership.Provider.ValidateUser(userName, password))
return false;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(500),
false,
FormsAuthentication.FormsCookiePath);// Path cookie valid for
// Encrypt the cookie using the machine key for secure transport
string hash = FormsAuthentication.Encrypt(ticket);
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, // Name of auth cookie
hash); // Hashed ticket
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent)
cookie.Expires = ticket.Expiration;
// Add the cookie to the list for outgoing response
if(HttpContext.Current !=null)
HttpContext.Current.Response.Cookies.Add(cookie);
FormsAuthentication.SetAuthCookie(userName, true);
return true;
}
If you are working with WCF, there's an easy way to implement security using X509 certificates. Implementing a binding with security mode 'Message' and clientCredentialType 'Username' it's possible to guarantee this security in a automated way.
The validation can be made through a class wich overrides a method Validate.
http://msdn.microsoft.com/en-us/library/aa702565.aspx
If your WS is going to be consumed through SOAP protocol, the you can implement the Security through the SOAP Header:
using System.Web.Services;
using System.Web.Services.Protocols;
namespace Domain.WS
{
[Serializable]
public class SoapWSHeader : System.Web.Services.Protocols.SoapHeader, ISoapWSHeader
{
public string UserId { get; set; }
public string ServiceKey { get; set; }
public ApplicationCode ApplicationCode { get; set; }
}
[WebService(Namespace = "http://domain.some.unique/")]
public class MyServices : System.Web.Services.WebService
{
public SoapWSHeader WSHeader;
private ServicesLogicContext _logicServices;
public MyServices() { _logicServices = new ServicesLogicContext(new LogicInfo() {...}); }
[WebMethod, SoapHeader("WSHeader", Direction = SoapHeaderDirection.InOut)]
public Result WSMethod1(Int32 idSuperior)
{
_logicServices.ThrowIfNotAuthenticate(WSHeader);
return _logicServices.WSMethod1(idSuperior) as Result;
}
}
}
namespace Domain.Logic
{
[Serializable]
public class ServicesLogicContext : ServicesLogicContextBase
{
protected ISoapWSHeader SoapWSHeader { get; set; }
public ServicesLogicContext(LogicInfo info) : base(info) {}
public IResult WSMethod1(Int32 idSuperior)
{
IResult result = null;
//-- method implementation here...
return result;
}
public void ThrowIfNotAuthenticate(ISoapWSHeader soapWSHeader) {
this.SoapWSHeader = soapWSHeader;
if (SoapWSHeader != null)
{
if (!ValidateCredentials(soapWSHeader))
{
throw new System.Security.SecurityException(Resources.ValidationErrorWrongCredentials);
}
}
else { throw new System.Security.SecurityException(Resources.ValidationErrorWrongWSHeader); }
}
private bool ValidateCredentials(ISoapWSHeader soapWSHeader) {
return (SoapWSHeader.UserId.Equals("USER_ID") && SoapWSHeader.ServiceKey.Equals("PSW_1"));
}
}
}
Note: this code is not complete, this only depicts the main aspects on how to use the SOAP Header.
Before you roll your own authentication, you might want to have a look at Web Services Enhancements (WSE) 2.0 SP3 for Microsoft .NET. It's an implementation of the WS-Security specification for .net.
google wse 2.0 or WS-Security for more links.
Related
We are trying to communicate with a REST server, which uses its own OAuth2 implementation.
This server is written by another company in Java, so I don't have much influence about it.
I've got all the necessary information, like Access Token URL, Refresh URL, Client Id, Client Secret, etc. I can already request an access token and then request some other data from this server, using the REST client Postman.
Now I'd like to use the ServiceStack client (version 4.5.14), to communicate with this server in C# .NET 4.6.2.
My problem is: All the examples I found, e.g. http://docs.servicestack.net/authentication-and-authorization#custom-authentication-and-authorization are either about the server-side or about authentication against Facebook or Google.
I already implemented my own CustomOAuth2Provider, setting the access token URL, ConsumerSecret, etc.
But how do I tell the JsonServiceClient, to use this Provider, before executing the specific request?
Thank you,
Daniel
Edit:
I read a lot of documentation and ServiceStack sourcecode, and I think my main problems are the following:
I abuse the ServiceStack Client to communicate with a non-ServiceStack application, which I can not modify.
Maybe the OAuth2 implementation of the third-party application is not 100% correct, as it expects authorization and token request in the same request.
But I got it working now and would like to show my solution here.
It still can be improved, e.g. it does not use the received refresh token right now.
public class ThirdPartyAuthenticator : IDisposable
{
// TODO: Move to config
public const string AccessTokenUrl = "";
public const string ConsumerKey = "";
public const string ConsumerSecret = "";
public const string Username = "";
public const string Password = "";
/// <summary>
/// Remember the last response, instance comprehensive so we do not need a new token for every request
/// </summary>
public static ServiceModel.ThirdPartyOAuth2Response LastOAuthResponse = null;
/// <summary>
/// This already authenticated client can be used for the data requests.
/// </summary>
public JsonServiceClient AuthenticatedServiceClient { get; set; }
public ThirdPartyAuthenticator()
{
if (LastOAuthResponse == null || (LastOAuthResponse.ExpiryDateTime < DateTime.Now)) // TODO: Use Refresh token?
{
// Get token first
JsonServiceClient authClient = new JsonServiceClient(AccessTokenUrl);
authClient.UserName = ConsumerKey;
authClient.Password = ConsumerSecret;
authClient.AlwaysSendBasicAuthHeader = true;
var request = new ServiceModel.ThirdPartyOAuth2Request();
request.Username = Username;
request.Password = Password;
// Use the Get URI, because server expects username + password as query parameter
LastOAuthResponse = authClient.Post<ServiceModel.ThirdPartyOAuth2Response>(request.ToGetUrl(), request);
}
// If no exception was thrown, we have a valid token here.
AuthenticatedServiceClient = new JsonServiceClient(AccessTokenUrl);
AuthenticatedServiceClient.BearerToken = LastOAuthResponse.AccessToken;
}
public void Dispose()
{
AuthenticatedServiceClient?.Dispose();
}
}
usage:
using (var foo = new ThirdPartyAuthenticator())
{
var response = foo.AuthenticatedServiceClient.Get(new ServiceModel.GetMyData() { SomeId = 10 });
}
OAuth providers require a browser to redirect to the OAuth provider site where Users are able to accept authentication with the App and any permissions it requires. Once the user accepts they're redirected back to your ServiceStack App where it will create an Authenticated User Session. The session id from the Authenticated User Session is what's configured on the ServiceStack client to establish authenticated requests.
Here are some Example Apps which use OAuth to Authenticate using a browser then capture the browser redirect to extract the session cookies and configure it on the C# Service Client where they're then able to make Authenticated requests:
https://github.com/ServiceStackApps/TechStacksAuth
https://github.com/ServiceStackApps/AndroidJavaChat
I have noticed that my ASP.NET Web Forms application based on Identity 2.2.1 fetches logged in user data from the database on every page request. Is this normal and designed behaviour? For performance reasons, user data might be cached in Session. I have double checked if I have not added extra code in master page which might cause this behaviour. But no, I am using standard Web Form template with no code added to the master page.
SQL statement executed on every page request:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Email] AS [Email],
[Extent1].[EmailConfirmed] AS [EmailConfirmed],
[Extent1].[PasswordHash] AS [PasswordHash],
[Extent1].[SecurityStamp] AS [SecurityStamp],
[Extent1].[PhoneNumber] AS [PhoneNumber],
[Extent1].[PhoneNumberConfirmed] AS [PhoneNumberConfirmed],
[Extent1].[TwoFactorEnabled] AS [TwoFactorEnabled],
[Extent1].[LockoutEndDateUtc] AS [LockoutEndDateUtc],
[Extent1].[LockoutEnabled] AS [LockoutEnabled],
[Extent1].[AccessFailedCount] AS [AccessFailedCount],
[Extent1].[UserName] AS [UserName]
FROM [dbo].[AspNetUsers] AS [Extent1]
WHERE [Extent1].[Id] = #p0
UPDATE 1
As application works in Azure using App Service and Azure SQL, proof for database query behind each page request was Application Insights, as per attached screenshot.
I have started to investigate further and have moved database to local environment. SQL Server Profiler shows there is in fact 10 queries to database on each page request. Those are SELECT to AspNetUsers, AspNetUserClaims, AspNetUserLogins, etc. Some of them are executed twice. This does not depend on master page. Pages not based on master page trigger same 10 queries as those based on one.
I have done few modifications to default Visual Studio template, as per below source code. I have double checked that new project based on same template does not trigger any database queries once user is logged in.
Modifications done:
additional fields in ApplicationUser class, added to database table via migrations
few configuration parameters
email service configuration
Source code:
Global_asax
Public Class Global_asax
Inherits HttpApplication
Sub Application_Start(sender As Object, e As EventArgs)
' Fires when the application is started
RouteConfig.RegisterRoutes(RouteTable.Routes)
BundleConfig.RegisterBundles(BundleTable.Bundles)
End Sub
End Class
Startup
Partial Public Class Startup
' For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301883
Public Sub ConfigureAuth(app As IAppBuilder)
'Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(AddressOf ApplicationDbContext.Create)
app.CreatePerOwinContext(Of ApplicationUserManager)(AddressOf ApplicationUserManager.Create)
app.CreatePerOwinContext(Of ApplicationSignInManager)(AddressOf ApplicationSignInManager.Create)
' Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(New CookieAuthenticationOptions() With {
.AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
.Provider = New CookieAuthenticationProvider() With {
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager, ApplicationUser)(
validateInterval:=TimeSpan.FromMinutes(0),
regenerateIdentity:=Function(manager, user) user.GenerateUserIdentityAsync(manager))},
.LoginPath = New PathString("/Account/Login"),
.ExpireTimeSpan = TimeSpan.FromMinutes(20),
.SlidingExpiration = True})
' Use a cookie to temporarily store information about a user logging in with a third party login provider
'app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie)
' Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
'app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5))
' Enables the application to remember the second login verification factor such as phone or email.
' Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
' This is similar to the RememberMe option when you log in.
'app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie)
' Uncomment the following lines to enable logging in with third party login providers
'app.UseMicrosoftAccountAuthentication(
' clientId:= "",
' clientSecret:= "")
'app.UseTwitterAuthentication(
' consumerKey:= "",
' consumerSecret:= "")
'app.UseFacebookAuthentication(
' appId:= "",
' appSecret:= "")
'app.UseGoogleAuthentication(New GoogleOAuth2AuthenticationOptions() With {
' .ClientId = "",
' .ClientSecret = ""})
End Sub
End Class
IdentityConfig.vb
Public Class EmailService
Implements IIdentityMessageService
Public Function SendAsync(message As IdentityMessage) As Task Implements IIdentityMessageService.SendAsync
' Plug in your email service here to send an email.
'Return Task.FromResult(0)
Dim client As New Net.Mail.SmtpClient(DTAppSettings.SendGrid_SMTPServer, 587)
Dim credentials As New Net.NetworkCredential(DTAppSettings.SendGrid_Username, DTAppSettings.SendGrid_Password)
client.Credentials = credentials
client.EnableSsl = True
Dim mailmessage As New Net.Mail.MailMessage With {
.From = New Net.Mail.MailAddress(DTAppSettings.SendGrid_FromAddress, DTAppSettings.SendGrid_FromName),
.Subject = message.Subject,
.Body = message.Body,
.IsBodyHtml = True
}
mailmessage.To.Add(message.Destination)
Return client.SendMailAsync(mailmessage)
End Function
End Class
Public Class SmsService
Implements IIdentityMessageService
Public Function SendAsync(message As IdentityMessage) As Task Implements IIdentityMessageService.SendAsync
' Plug in your SMS service here to send a text message.
Return Task.FromResult(0)
End Function
End Class
' Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
Public Class ApplicationUserManager
Inherits UserManager(Of ApplicationUser)
Public Sub New(store As IUserStore(Of ApplicationUser))
MyBase.New(store)
End Sub
Public Shared Function Create(options As IdentityFactoryOptions(Of ApplicationUserManager), context As IOwinContext) As ApplicationUserManager
Dim manager = New ApplicationUserManager(New UserStore(Of ApplicationUser)(context.[Get](Of ApplicationDbContext)()))
' Configure validation logic for usernames
manager.UserValidator = New UserValidator(Of ApplicationUser)(manager) With {
.AllowOnlyAlphanumericUserNames = False,
.RequireUniqueEmail = True
}
' Configure validation logic for passwords
manager.PasswordValidator = New PasswordValidator() With {
.RequiredLength = 6,
.RequireNonLetterOrDigit = True,
.RequireDigit = True,
.RequireLowercase = True,
.RequireUppercase = True
}
' Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user.
' You can write your own provider and plug in here.
'manager.RegisterTwoFactorProvider("Phone Code", New PhoneNumberTokenProvider(Of ApplicationUser)() With {
' .MessageFormat = "Your security code is {0}"
'})
'manager.RegisterTwoFactorProvider("Email Code", New EmailTokenProvider(Of ApplicationUser)() With {
' .Subject = "Security Code",
' .BodyFormat = "Your security code is {0}"
'})
' Configure user lockout defaults
manager.UserLockoutEnabledByDefault = True
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5)
manager.MaxFailedAccessAttemptsBeforeLockout = 5
manager.EmailService = New EmailService()
manager.SmsService = New SmsService()
Dim dataProtectionProvider = options.DataProtectionProvider
If dataProtectionProvider IsNot Nothing Then
manager.UserTokenProvider = New DataProtectorTokenProvider(Of ApplicationUser)(dataProtectionProvider.Create("ASP.NET Identity")) With {
.TokenLifespan = TimeSpan.FromHours(1)
}
End If
Return manager
End Function
End Class
Public Class ApplicationSignInManager
Inherits SignInManager(Of ApplicationUser, String)
Public Sub New(userManager As ApplicationUserManager, authenticationManager As IAuthenticationManager)
MyBase.New(userManager, authenticationManager)
End Sub
Public Overrides Function CreateUserIdentityAsync(user As ApplicationUser) As Task(Of ClaimsIdentity)
Return user.GenerateUserIdentityAsync(DirectCast(UserManager, ApplicationUserManager))
End Function
Public Shared Function Create(options As IdentityFactoryOptions(Of ApplicationSignInManager), context As IOwinContext) As ApplicationSignInManager
Return New ApplicationSignInManager(context.GetUserManager(Of ApplicationUserManager)(), context.Authentication)
End Function
End Class
The problem is related to this bit of code:
.OnValidateIdentity = SecurityStampValidator.OnValidateIdentity(Of ApplicationUserManager,
ApplicationUser)(validateInterval:=TimeSpan.FromMinutes(0),
Try a larger value, such as .FromMinutes(15)
Since the validateInterval is 0, it's basically re-validating the Identity information on every page load.
I have a .NET Webforms site thanks needs to post to my MVC Application which currently sits inside the Webform site as a separate application.
The Webform application need to POST some sensitive values to the MVC Application.
Is there a way to generate a AntiForgeryToken() in my WebForms Application so it can be passed with the form post.
Otherwise does anyone know of any other custom anti forgery code that will allow me to do something similar to the MVC's AntiForgeryValidation.
Implementing it yourself is not too difficult.
Generate a GUID
Put it in a hidden field
Also put it in Session or Cookie (in the latter case, with some anti-tamper protection)
At the start of processing the form compare the field and stored token.
(If you look at the implementation of MVC, there is very little more to it. A few helper methods is all you need.)
This is an old question, but the latest Visual Studio 2012 ASP.NET template for web forms includes anti CSRF code baked into the master page. If you don't have the templates, here's the code it generates:
Protected Sub Page_Init(sender As Object, e As System.EventArgs)
' The code below helps to protect against XSRF attacks
Dim requestCookie As HttpCookie = Request.Cookies(AntiXsrfTokenKey)
Dim requestCookieGuidValue As Guid
If ((Not requestCookie Is Nothing) AndAlso Guid.TryParse(requestCookie.Value, requestCookieGuidValue)) Then
' Use the Anti-XSRF token from the cookie
_antiXsrfTokenValue = requestCookie.Value
Page.ViewStateUserKey = _antiXsrfTokenValue
Else
' Generate a new Anti-XSRF token and save to the cookie
_antiXsrfTokenValue = Guid.NewGuid().ToString("N")
Page.ViewStateUserKey = _antiXsrfTokenValue
Dim responseCookie As HttpCookie = New HttpCookie(AntiXsrfTokenKey) With {.HttpOnly = True, .Value = _antiXsrfTokenValue}
If (FormsAuthentication.RequireSSL And Request.IsSecureConnection) Then
responseCookie.Secure = True
End If
Response.Cookies.Set(responseCookie)
End If
AddHandler Page.PreLoad, AddressOf master_Page_PreLoad
End Sub
Private Sub master_Page_PreLoad(sender As Object, e As System.EventArgs)
If (Not IsPostBack) Then
' Set Anti-XSRF token
ViewState(AntiXsrfTokenKey) = Page.ViewStateUserKey
ViewState(AntiXsrfUserNameKey) = If(Context.User.Identity.Name, String.Empty)
Else
' Validate the Anti-XSRF token
If (Not DirectCast(ViewState(AntiXsrfTokenKey), String) = _antiXsrfTokenValue _
Or Not DirectCast(ViewState(AntiXsrfUserNameKey), String) = If(Context.User.Identity.Name, String.Empty)) Then
Throw New InvalidOperationException("Validation of Anti-XSRF token failed.")
End If
End If
End Sub
The C# version of Ian Ippolito answer here:
public partial class SiteMaster : MasterPage
{
private const string AntiXsrfTokenKey = "__AntiXsrfToken";
private const string AntiXsrfUserNameKey = "__AntiXsrfUserName";
private string _antiXsrfTokenValue;
protected void Page_Init(object sender, EventArgs e)
{
// The code below helps to protect against XSRF attacks
var requestCookie = Request.Cookies[AntiXsrfTokenKey];
Guid requestCookieGuidValue;
if (requestCookie != null && Guid.TryParse(requestCookie.Value, out requestCookieGuidValue))
{
// Use the Anti-XSRF token from the cookie
_antiXsrfTokenValue = requestCookie.Value;
Page.ViewStateUserKey = _antiXsrfTokenValue;
}
else
{
// Generate a new Anti-XSRF token and save to the cookie
_antiXsrfTokenValue = Guid.NewGuid().ToString("N");
Page.ViewStateUserKey = _antiXsrfTokenValue;
var responseCookie = new HttpCookie(AntiXsrfTokenKey)
{
HttpOnly = true,
Value = _antiXsrfTokenValue
};
if (FormsAuthentication.RequireSSL && Request.IsSecureConnection)
{
responseCookie.Secure = true;
}
Response.Cookies.Set(responseCookie);
}
Page.PreLoad += master_Page_PreLoad;
}
protected void master_Page_PreLoad(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Set Anti-XSRF token
ViewState[AntiXsrfTokenKey] = Page.ViewStateUserKey;
ViewState[AntiXsrfUserNameKey] = Context.User.Identity.Name ?? String.Empty;
}
else
{
// Validate the Anti-XSRF token
if ((string)ViewState[AntiXsrfTokenKey] != _antiXsrfTokenValue
|| (string)ViewState[AntiXsrfUserNameKey] != (Context.User.Identity.Name ?? String.Empty))
{
throw new InvalidOperationException("Validation of Anti-XSRF token failed.");
}
}
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
WebForms has a pretty similar analog in Page.ViewStateUserKey. By setting that to a per-user value (most choose HttpSessionState.SessionId), WebForms will validate the ViewState1 as part of the MAC check.
overrides OnInit(EventArgs e) {
base.OnInit(e);
ViewStateUserKey = Session.SessionId;
}
1 There are scenarios where ViewStateUserKey will not help. Mainly, they boil down to doing dangerous things with GET requests (or in Page_Load without checking IsPostback), or disabling ViewStateMAC.
You can use reflection to get at the MVC methods used to set the cookie and matching form input used for the MVC validation. That way you can have an MVC action with [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken] attributes that you can post to from a WebForms generated page.
See this answer: Using an MVC HtmlHelper from a WebForm
Update2
This post is getting old but still relevant.. Below is whe way I solved it. I marked the other guys answer because I think it answers the question better. I'm calling a similar method(I'am about to refactor:)) in accountcontroller. The string should be a list... I think you get it.
/// <summary>
/// Use this method when an action fails due to lack of priviligies. It will redirect user to facebook with provided permission request.
/// Refactor to handle list of request.
/// </summary>
/// <param name="permission"></param>
private static void AddAdditionalPermissions(string permission)
{
System.Diagnostics.Trace.TraceInformation(permission + " not authorized for user.");
string facebook_urlAuthorize_base = "https://graph.facebook.com/oauth/authorize";
string scope = permission; //see: https://developers.facebook.com/docs/authentication/permissions/ for extended permissions
string urlAuthorize = facebook_urlAuthorize_base;
urlAuthorize += "?client_id=" + AppId;
urlAuthorize += "&redirect_uri=" + "https://fbd.anteckna.nu/";
urlAuthorize += "&scope=" + scope;
//redirect the users browser to Facebook to ask the user to authorize our Facebook application
HttpContext.Current.Response.Redirect(urlAuthorize, true); //this cannot be done using WebRequest since facebook may need to show dialogs in the users browser
}
Then every method making a call to facebook like /me/home with facebok C# SDK catches FacebookOAuthException and redirects to the folling method. This is how we apply the best practise of not asking permissions from users up front but when needed. This method should have aredirect url that matches as well but we've just get going :)
Hope it helps!
/// <summary>
/// Check for what permissions to request or different ways to handle FacebookOAuthExceptions.
/// </summary>
/// <param name="foae">The exception object</param>
public static void HandleAuthorizationsExceptions(FacebookOAuthException foae)
{
if (foae.Message.Contains("publish_permissions"))
{
AddAdditionalPermissions("publish_permissions");
}
else if (foae.Message.Contains("read_stream"))
{
AddAdditionalPermissions("read_stream");
}
else
{
System.Diagnostics.Trace.TraceError("Unhandled error at:" + foae.StackTrace);
}
}
Update: This behaviour is caused by .Net oauth implementation which has the scope hard coded in a sealed class. Added figure 4 to show the request parameter where the lack of additional scopes besides "email"(which is sent with all requests by .net oauth provider). Adding ",publish_stream" to the query string gives me the wanted behaviour. Anyone knows how to achieve this?
Please do not submit answers or comments about facebook best practices or alternative solutions. I have an alternative solution but would like this to work with default registerfacebookclient parameters. I have updated the application to oly use publish_stream according to the two answers specifying on what permissions I'm asking for.
figure 4
Original question:
I'm setting up an application (C#.Net4.5 MVC4, razor views) which need pretty much all available user permissions from facebook.
You can see code examples below how I have set it all up.
The problem is that when clicking "okay" in figure 1, Facebook sends me back to my application. As I understand there should be an additional screen(figure2) asking for the "heavier" permissions. As of now I only get the permissions stated in figure one. That part works...
Figure 1
Figure 2
So, using basic
AuthConfig.cs
var facebooksocialData = new Dictionary<string, object>();
facebooksocialData.Add("scope", "email,publish_stream,read_stream,publish_actions,manage_pages,create_event,offline_access");
OAuthWebSecurity.RegisterFacebookClient(
appId: "165359673639901",
appSecret: "15091cb2094a1996ae6c7b324f0300e6",
displayName: "Facebook",
extraData: facebooksocialData);
This is how I handle the response but here facebook has not prompted the user for the extended permissions but only for email,
AccountController.cs
//
// GET: /Account/ExternalLoginCallback
[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
// Save the accesstoken into session
Session["accesstoken"] = result.ExtraData["accesstoken"];
Session["id"] = result.ExtraData["id"];
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false))
{
return RedirectToLocal(returnUrl);
}
if (User.Identity.IsAuthenticated)
{
// If the current user is logged in add the new account
OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name);
return RedirectToLocal(returnUrl);
}
else
{
// User is new, ask for their desired membership name
string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId);
ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
ViewBag.ReturnUrl = returnUrl;
return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData });
}
}
The closest to an answer I could find was a wp plugin which had the same issue. Their problem was solved by setting domain to localhost. This is how my application is set up.
I got the same problem. As you did, I configured the RegisterFacebookClient with dictionary to define my app's scope, and unfortunately the request didn't include the scope as I configured. So I found that. It seems that would work, but it wasn't enough. So I found this.
So here is what solve my problems:
First of all I added this new client to my code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using DotNetOpenAuth.AspNet;
using Newtonsoft.Json;
namespace MyApp.UI.Infrastructure
{
public class FacebookScopedClient : IAuthenticationClient
{
private string appId;
private string appSecret;
private string scope;
private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id=";
public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?";
public const string graphApiMe = "https://graph.facebook.com/me?";
private static string GetHTML(string URL)
{
string connectionString = URL;
try
{
System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString);
myRequest.Credentials = CredentialCache.DefaultCredentials;
//// Get the response
WebResponse webResponse = myRequest.GetResponse();
Stream respStream = webResponse.GetResponseStream();
////
StreamReader ioStream = new StreamReader(respStream);
string pageContent = ioStream.ReadToEnd();
//// Close streams
ioStream.Close();
respStream.Close();
return pageContent;
}
catch (Exception)
{
}
return null;
}
private IDictionary<string, string> GetUserData(string accessCode, string redirectURI)
{
string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" + HttpUtility.UrlEncode(redirectURI) + "&client_secret=" + appSecret + "&code=" + accessCode);
if (token == null || token == "")
{
return null;
}
string access_token = token.Substring(token.IndexOf("access_token="), token.IndexOf("&"));
string data = GetHTML(graphApiMe + "fields=id,name,email,username,gender,link&" + access_token);
// this dictionary must contains
Dictionary<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return userData;
}
public FacebookScopedClient(string appId, string appSecret, string scope)
{
this.appId = appId;
this.appSecret = appSecret;
this.scope = scope;
}
public string ProviderName
{
get { return "facebook"; }
}
public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl)
{
string url = baseUrl + appId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + scope;
context.Response.Redirect(url);
}
public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context)
{
string code = context.Request.QueryString["code"];
string rawUrl = context.Request.Url.OriginalString;
//From this we need to remove code portion
rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");
IDictionary<string, string> userData = GetUserData(code, rawUrl);
if (userData == null)
return new AuthenticationResult(false, ProviderName, null, null, null);
string id = userData["id"];
string username = userData["username"];
userData.Remove("id");
userData.Remove("username");
AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
return result;
}
}
}
I put it on a folder "infrastructure" in my asp.net solution with oder stuff, next I change my old configuration, in order to use the new facebook client, as follows:
Old code:
OAuthWebSecurity.RegisterFacebookClient(
appId: "<app-id>",
appSecret: "<app-secret>",
displayName: "Facebook",
extraData: facebookExtraData);
New Code:
OAuthWebSecurity.RegisterClient(
new FacebookScopedClient(
"<app-id>",
"<app-secret>",
"scope"),
"Facebook",
null);
That's it. It may help you, as helped me.
Is your app registered for these scopes? I'm familiar with Google OAuth, they have a separate scope that maps to one permission. Your app should be registered for the scopes, in order to get the 2nd window. Else, you'll have access only to the public info that your 1st popup asks for..
First of all, offline_access does not exist any more, so it from the permissions you are asking for.
"[app] which need pretty much all available user permissions from facebook"
Facebook actively discourages asking for heaps of permissions straight from the beginning "just in case" because they might be needed later. One should only ask for an extended permission when it is actually needed for an action the user just triggered for the first time.
Also, you are supposed to ask for "read" and "write" permissions separately.
I don't know if these aspects are actually triggering your error - but I know that Facebook has already been sending out developer alerts for the read/write thing; although an FB employee confirmed that those alerts can be ignored for now, they might start enforcing this at some point in the future.
I have a file upload in my site which is done using uploadify it uses a ashx page to upload file to database.It works fine in IE but in Mozilla the context.Session is getting null.I have also used IReadOnlySessionState to read session.
how can i get session in Mozilla like IE.
here is the ashx code i have done
public class Upload : IHttpHandler, IReadOnlySessionState
{
HttpContext context;
public void ProcessRequest(HttpContext context)
{
string UserID = context.Request["UserID"];
context.Response.ContentType = "text/plain";
context.Response.Expires = -1;
XmlDocument xDoc = new XmlDocument();
HttpPostedFile postedFile = context.Request.Files["Filedata"];
try
{
if (context.Session["User"] == null || context.Session["User"].ToString() == "")
{
context.Response.Write("SessionExpired");
context.Response.StatusCode = 200;
}
else
{
// does the uploading to database
}
}
}
}
In IE Context.Session["User"] always have the value but in Mozilla it is always null
You need to add sessionId to uploadify post params and restore ASP.NET_SessionId cookie on the server side on global.asax at OnBeginRequest. It is actually bug with flash and cookies.
I have created module for session and auth cookie restore, to get work flash and asp.net session, so i think it will be useful for your:
public class SwfUploadSupportModule : IHttpModule
{
public void Dispose()
{
// clean-up code here.
}
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(OnBeginRequest);
}
private void OnBeginRequest(object sender, EventArgs e)
{
var httpApplication = (HttpApplication)sender;
/* we guess at this point session is not already retrieved by application so we recreate cookie with the session id... */
try
{
string session_param_name = "ASPSESSID";
string session_cookie_name = "ASP.NET_SessionId";
if (httpApplication.Request.Form[session_param_name] != null)
{
UpdateCookie(httpApplication, session_cookie_name, httpApplication.Request.Form[session_param_name]);
}
else if (httpApplication.Request.QueryString[session_param_name] != null)
{
UpdateCookie(httpApplication, session_cookie_name, httpApplication.Request.QueryString[session_param_name]);
}
}
catch
{
}
try
{
string auth_param_name = "AUTHID";
string auth_cookie_name = FormsAuthentication.FormsCookieName;
if (httpApplication.Request.Form[auth_param_name] != null)
{
UpdateCookie(httpApplication, auth_cookie_name, httpApplication.Request.Form[auth_param_name]);
}
else if (httpApplication.Request.QueryString[auth_param_name] != null)
{
UpdateCookie(httpApplication, auth_cookie_name, httpApplication.Request.QueryString[auth_param_name]);
}
}
catch
{
}
}
private void UpdateCookie(HttpApplication application, string cookie_name, string cookie_value)
{
var httpApplication = (HttpApplication)application;
HttpCookie cookie = httpApplication.Request.Cookies.Get(cookie_name);
if (null == cookie)
{
cookie = new HttpCookie(cookie_name);
}
cookie.Value = cookie_value;
httpApplication.Request.Cookies.Set(cookie);
}
}
Also than you need register above module at web.config:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="SwfUploadSupportModule" type="namespace.SwfUploadSupportModule, application name" />
</modules>
</system.webServer>
Context.Session is null.. because connection to HttpHandler has another Context.Session
(debug and try: Context.Session.SessionId in where is the fileInput is different from Context.Session.SessionId in Upload.ashx)!
I suggest a workaround: pass a reference to the elements you need in the second session ( in my sample i pass the original SessionId using sessionId variable)
....
var sessionId = "<%=Context.Session.SessionID%>";
var theString = "other param,if needed";
$(document).ready(function () {
$('#fileInput').uploadify({
'uploader': '<%=ResolveUrl("~/uploadify/uploadify.swf")%>',
'script': '<%=ResolveUrl("~/Upload.ashx")%>',
'scriptData': { 'sessionId': sessionId, 'foo': theString },
'cancelImg': '<%=ResolveUrl("~/uploadify/cancel.png")%>',
....
and use this items in .ashx file.
public void ProcessRequest(HttpContext context)
{
try
{
HttpPostedFile file = context.Request.Files["Filedata"];
string sessionId = context.Request["sessionId"].ToString();
....
If you need to share complex elements use Context.Application instead of Context.Session, using original SessionID: Context.Application["SharedElement"+SessionID]
It's likely to be something failing to be set by the server or sent back on the client.
Step back to a lower level - use a network diagnostic tool such as Fiddler or Wireshark to examine the traffic being sent to/from your server and compare the differences between IE and Firefox.
Look at the headers to ensure that cookies and form values are being sent back to the server as expected.
I have created a function to check session have expired and then pass that as a parameter in script-data of uploadify and in ashx file i check that parameter to see whether session exists or not.if it returns session have expired then upload will not take place.It worked for me. Did not find any issues using that. hope that solve my issue
I had a similar problem with an .ashx file. The solution was that the handler has to implement IReadOnlySessionState (for read-only access) or IRequiresSessionState (for read-write access). eg:
public class SwfUploadSupportModule : IHttpHandler, IRequiresSessionState { ... }
These Interfaces do not need any additional code but act as markers for the framework.
Hope that this helps.
Jonathan