I'm aware that the authentication on the webservicehost class does not adhere fully to authentication standards (returns 403 forbidden rather than prompting for another set of credentials when the user enters incorrect credentials).
I'd still like to implement this basic authentication (username and password at the start of the session, HTTPS unnecessary - see picture below) as it suits my needs for a small home project.
The code I have for myService is as follows:
Imports System.IO
Imports System.Text
Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.ServiceModel.Channels
<ServiceContract()>
Public Class myService
<OperationContract(), WebGet(UriTemplate:="/xml/{argument1}/{argument2}")>
Public Function XML(argument1 As String, argument2 As String) As Stream
requestCounter += 1
Console.WriteLine("xml data request at " & DateTime.Now.ToString() & ", request count= " & requestCounter)
Console.WriteLine(WebOperationContext.Current.IncomingRequest.UserAgent.ToString())
Return _ReturnXML("<xmlresponse><data><argument1>" & argument1 & "</argument1><argument2>" & argument2 & "</argument2></data><server><serverlivesince>" & serverStart.ToString() & "</serverlivesince><pageservetime>" & DateTime.Now.ToString() & "</pageservetime><requestcount>" & requestCounter & "</requestcount></server></xmlresponse>")
'returns the first two parameters, and the time and date
End Function
Private Shared Function _ReturnXML(_result As String) As Stream
Dim data = Encoding.UTF8.GetBytes(_result)
WebOperationContext.Current.OutgoingResponse.ContentType = "text/xml; charset=utf-8"
WebOperationContext.Current.OutgoingResponse.ContentLength = data.Length
Return New MemoryStream(data)
End Function
End Class
I then have similar code to return HTML as well as accept other parameter combinations.
In my Main class I've instantiated and opened this service as:
Dim varWebService = New WebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))
varWebService.Open()
Could anyone provide me with code to implement this simple authentication? Or point me to a thorough tutorial? Thanks for any help
You can write a custom WebServiceHost by inheriting from it and change some default parameters like below.
The only change in your code would be
Dim varWebService = New AuthenticatedWebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IdentityModel;
using System.IdentityModel.Selectors;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Security;
using System.ServiceModel.Description;
namespace StackOverflow
{
public class AuthenticatedWebServiceHost : WebServiceHost
{
public AuthenticatedWebServiceHost(Type type, Uri url)
{
IDictionary<string, ContractDescription> desc = null;
base.InitializeDescription(type, new UriSchemeKeyedCollection());
base.CreateDescription(out desc);
var val = desc.Values.First();
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
base.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
base.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();
base.AddServiceEndpoint(val.ContractType, binding, url);
}
//Possible next question:
//"How can I get the name of the authenticated user?"
public static string UserName
{
get
{
if (OperationContext.Current == null) return null;
if (OperationContext.Current.ServiceSecurityContext == null) return null;
if (OperationContext.Current.ServiceSecurityContext.PrimaryIdentity == null) return null;
return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name;
}
}
public class CustomUserNamePasswordValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
//Your logic to validate username/password
if (userName != password)
throw new SecurityAccessDeniedException();
}
}
}
}
The answer provided by I4V worked like a charm, converted to VB and copied below in case anyone else needs it in future after spending many hours hunting the web.
The Line to call it is as per the code provided by I4V.
Dim varWebService = New AuthenticatedWebServiceHost(GetType(MyWebService), New Uri("http://0.0.0.0/"))
VB.Net Code
Imports System.IdentityModel.Selectors
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports System.ServiceModel.Security
Imports System.ServiceModel.Web
Public Class AuthenticatedWebServiceHost
Inherits WebServiceHost
Public Sub New(ByVal type As Type, ByVal url As Uri)
Dim desc As IDictionary(Of String, ContractDescription) = Nothing
MyBase.InitializeDescription(type, New UriSchemeKeyedCollection())
MyBase.CreateDescription(desc)
Dim val = desc.Values.First()
Dim binding As WebHttpBinding = New WebHttpBinding()
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic
MyBase.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom
MyBase.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = New CustomUserNamePasswordValidator()
MyBase.AddServiceEndpoint(val.ContractType, binding, url)
End Sub
Public Shared ReadOnly Property UserName As String
Get
If OperationContext.Current Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity Is Nothing Then Return Nothing
Return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
End Get
End Property
Public Class CustomUserNamePasswordValidator
Inherits UserNamePasswordValidator
Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
If userName <> password Then Throw New SecurityAccessDeniedException()
End Sub
End Class
End Class
Shaydo, you are the best! Thank you! That is what I searched for for weeks!
I expanded the vb Code in order to use it with https:
VB.NET:
Public Class AuthenticatedWebServiceHost
Inherits WebServiceHost
Public Sub New(ByVal type As Type, ByVal url As Uri, MyThumbprint As String)
Dim desc As IDictionary(Of String, ContractDescription) = Nothing
MyBase.InitializeDescription(type, New UriSchemeKeyedCollection())
MyBase.CreateDescription(desc)
Dim val = desc.Values.First()
Dim binding As WebHttpBinding = New WebHttpBinding()
'binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly
binding.Security.Mode = BasicHttpsSecurityMode.TransportWithMessageCredential
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic
MyBase.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom
MyBase.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = New CustomUserNamePasswordValidator()
MyBase.Credentials.ClientCertificate.SetCertificate(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine, System.Security.Cryptography.X509Certificates.StoreName.My, System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, MyThumbprint)
MyBase.AddServiceEndpoint(val.ContractType, binding, url)
End Sub
Public Shared ReadOnly Property UserName As String
Get
If OperationContext.Current Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext Is Nothing Then Return Nothing
If OperationContext.Current.ServiceSecurityContext.PrimaryIdentity Is Nothing Then Return Nothing
Return OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name
End Get
End Property
Public Class CustomUserNamePasswordValidator
Inherits UserNamePasswordValidator
Public Overrides Sub Validate(ByVal userName As String, ByVal password As String)
If userName <> password Then
Console.WriteLine("Error: Access denied")
Throw New SecurityAccessDeniedException()
End If
End Sub
End Class
End Class
Related
in my current project, i was so stupid to make an API which expects the variable private. in the controller it gets mapped to isPrivate, but now i wanted to make a commandObject (#validatable) to check if everything is valid. How can i map the private variable using autobinding on isPrivate?
#Validateable
class EventCommand{
boolean isPrivate
boolean fullDay
String title
String location
String description
static constraints = {
location(nullable: true)
description(nullable: true)
fullDay (nullable: false)
isPrivate(nullable: false)
title (blank: true, nullable: false)
}
}
and the code where the databinding happens (inside a grails controllor):
def add() {
def jsonData = request.JSON
EventCommand command = new EventCommand(jsonData)
if(!command.validate()){
throw new QuivrException(command)
}
boolean isPrivate = jsonData.private
//some things happen, uninmportant
}
i already tried using the #BindUsing annotation, but i always keep getting the error that EventCommand has no property named "private" (the matches works, but he tries to match private to something none-existant)
is there any way to solve this without changing the received private to isPrivated (already got old versions of the application and got 20% not on latest version)
Well from your jsonData remove the private property and then do the binding and after binding add the private manually from a variable in which you should be getting the private value.
Hence your add could be something like
def add() {
def jsonData = request.JSON
String isPrivate = jsonData.private
jsonData.remove("private")
EventCommand command = new EventCommand(jsonData)
if(!command.validate()){
throw new QuivrException(command)
}
command.isPrivate = isPrivate as Boolean
}
I am not allowed to install fiddler at work so I am kind of flying blind.
I am running the web api and the web executable on local host through two separate instances of visual studio
I am fairly certain my Web API is working ok I type the URL manually into a web browser it asks me for user Id and password and then returns my JSON.
The web executable that calls the web api also works fine until I attempted to add BASIC authentication to the controller method now I am receiving a 401 error.
here is my code from the executable.
Public Function get_vsmric_webApi(ByRef sErrorDescription As String) As Boolean
Try
Using proxy As New WebClient()
Dim myurl As String = ConfigurationManager.AppSettings("WEBAPI_URL") & "vsmric"
Dim userName As String = "QBERT"
Dim passWord As String = "Qb3RT!"
Dim credentials As String = Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + ":" + passWord))
proxy.Headers(HttpRequestHeader.Authorization) = "BASIC" + credentials
Dim json As String = proxy.DownloadString(myurl)
Dim rics As List(Of DB2VSMRIC) = JsonConvert.DeserializeObject(Of List(Of DB2VSMRIC))(json)
Dim list As List(Of DB2VSMRIC) = rics.Where(Function(p) HasData(p.Cage)).ToList
If list.Count < 1 Then
sErrorDescription = "No VSMRIC w/Cage records found."
Else
dictShipFrom = New Dictionary(Of String, String)
dictShipFrom = list.ToDictionary(Function(p) p.Ric, Function(p) p.Dodaac)
dictCage = New Dictionary(Of String, String)
dictCage = list.ToDictionary(Function(p) p.Ric, Function(p) p.Cage)
End If
End Using
Catch ex As Exception
sErrorDescription = "Exception in get_vsmric_webApi(), " & ex.Message
Return False
Finally
End Try
Return True
End Function
here is the controller method on the web api
[CustomAuthentication]
[CustomAuthorization("qbert")]
public class VSMRICController : ApiController
{
/// <summary>
/// Returns all records in the DB2 VSM RIC table
/// </summary>
/// <param name="id">The ID of the data.</param>
public IEnumerable<DB2VSMRIC> Get()
{
return DB2VSMRICRepository.getAll();
}
here is the filter (for authentication)
public class CustomAuthenticationAttribute : Attribute, IAuthenticationFilter
{
// the job of the AuthenticateAsync method is to examine the request to see whether it contains
// the information that is required to identify a user. Information about the request is provided
// through an instance of the HttpAuthenticationContext class.
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
context.Principal = null;
AuthenticationHeaderValue authentication = context.Request.Headers.Authorization;
if (authentication != null && authentication.Scheme == "Basic")
{
string[] authData = Encoding.ASCII.GetString(Convert.FromBase64String(
authentication.Parameter)).Split(':');
context.Principal
= ApiManager.AuthenticateUser(authData[0], authData[1]);
}
if (context.Principal == null)
{
context.ErrorResult
= new UnauthorizedResult(new AuthenticationHeaderValue[]{
new AuthenticationHeaderValue("Basic")}, context.Request);
}
return Task.FromResult<object>(null);
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult<object>(null);
}
public bool AllowMultiple
{
get { return false; }
}
}
Again I'm fairly confident the Web API is working fine as I can get to JSON by directly navigating to the url and providing credentials in any web browser. I'm thinking I am doing something wrong when I set up the header in the executable. any thoughts? (I am running everything locally via 2 instance of visual studio)
The problem is on the line where you set the basic authentication. It should be
... = "Basic " + credentials
instead of
... = "BASIC" + credentials
Case sensitive and a space.
Happy coding.
I have the below custom module in an asp.net web application. The module works from chrome on my PC but when I try to access a video from Android I get an error because the userid (_context.User) is not set. This all occurs after I have logged into the application. Therefore _context.User should be set.
I would have thought because at this level you are dealing with HTTP Requests and Responses that the module would work for any device.
I have two questions.
Is there a way to make this work?
What is the difference between the requests sent by the Android tablet and PC to cause this issue?
Public Class VideoSecurityModule
Implements IHttpModule
Implements IRequiresSessionState
Private WithEvents _context As HttpApplication
Public Sub Dispose() Implements IHttpModule.Dispose
End Sub
Dim myUserManager As UserManager
Public Sub Init(ByVal context As HttpApplication) Implements IHttpModule.Init
_context = context
myUserManager = New UserManager
End Sub
Public Sub Log(value As String, ParamArray args As Object())
Dim message As String = String.Format(value, args)
Dim messagePrefix As String = String.Format("{0},{1},{2},{3}", UserAgent, Url, SessionId, message)
LogManager.WriteMessage(messagePrefix, "")
End Sub
Private ReadOnly Property UserAgent() As String
Get
Try
Return _context.Request.UserAgent
Catch ex As Exception
Return "No User Agent"
End Try
End Get
End Property
Private ReadOnly Property Url() As String
Get
Try
Return _context.Request.Url.PathAndQuery
Catch ex As Exception
Return "No URL"
End Try
End Get
End Property
Private ReadOnly Property SessionId() As String
Get
Try
Return __context.Session.SessionID
Catch ex As Exception
Return "No URL"
End Try
End Get
End Property
Public ReadOnly Property IsReusable() As Boolean
' IsReusable must be set to false since class has a member!
Get
Return True
End Get
End Property
Public Sub OnAuthorizeRequest(ByVal source As Object, ByVal e As EventArgs) Handles _context.AuthorizeRequest
If IsVideoUrl() Then
Dim userId As Integer = GetLoggedInUsersId()
Log("UserRequiresAuthorization({0}): {1}", userId, UserRequiresAuthorization(userId))
Log("UserIsAssignedToCourseContainingVideo({0}): {1}", userId, UserIsAssignedToCourseContainingVideo(userId))
' if user is not assigned to a course that utilizes the video or the user is not in role super user or system admin
If (UserRequiresAuthorization(userId) AndAlso Not UserIsAssignedToCourseContainingVideo(userId)) Then
SendAuthenticationRequiredResponse()
End If
End If
End Sub
Private Function GetLoggedInUsersId() As Integer
Dim userId As Integer = 0
If (Not _context.User Is Nothing) Then
userId = myUserManager.GetUserIdByUserName(_context.User.Identity.Name)
End If
Log("userId:{0}", userId)
Return userId
End Function
Private Sub SendAuthenticationRequiredResponse()
Const networkAuthenticationRequiredStatusCode As Integer = 511
_context.Response.StatusCode = networkAuthenticationRequiredStatusCode
_context.Response.ClearContent()
_context.Response.Write("UnAuthorized User")
Log("UnAuthorized User: {0}", "")
_context.Response.End()
End Sub
Private Function IsVideoUrl() As Boolean
Dim fileLocation As String = System.Configuration.ConfigurationManager.AppSettings("VideoLocation")
Log("url:{0}", _context.Request.Url)
Log("IsVideoUrl:{0}", _context.Request.FilePath.ToLower().Contains(fileLocation.ToLower()))
Return _context.Request.FilePath.ToLower().Contains(fileLocation.ToLower())
End Function
Private Function UserDoesNotRequireAuthorization(userId As Integer) As Boolean
Return myUserManager.IsSysAdmin(userId) OrElse myUserManager.IsSuperUser(userId)
End Function
Private Function UserRequiresAuthorization(userId As Integer) As Boolean
Dim result As Boolean = Not UserDoesNotRequireAuthorization(userId)
Return result
End Function
''' <summary>
''' Returns true if the in-putted user (logged in user) has been allocated to a course that utilize video specified in this request
''' </summary>
Private Function UserIsAssignedToCourseContainingVideo(userId As Integer) As Boolean
' ... code removed for clarity
End Function
End Class
This is a known issue with a number versions of android. My limited research suggests that video requests are passed to a separate component in android called stagefright. Stagefright can not handle cookies and therefore no authentication cookie is passed to the web application resulting in the _context.User property not being set.
The following links provide more in depth details.
Do Mobile Browsers send httpOnly cookies via the HTML5 Audio-Tag?
https://code.google.com/p/android/issues/detail?id=17553
I have created my own Authorize attribute called Authorise...
Imports System.Security.Principal
<AttributeUsage(AttributeTargets.Method Or AttributeTargets.[Class], Inherited:=True, AllowMultiple:=True)>
Public Class AuthoriseAttribute
Inherits AuthorizeAttribute
Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
Dim CookieName As String = FormsAuthentication.FormsCookieName
If Not filterContext.HttpContext.User.Identity.IsAuthenticated OrElse filterContext.HttpContext.Request.Cookies Is Nothing OrElse filterContext.HttpContext.Request.Cookies(CookieName) Is Nothing Then
HandleUnauthorizedRequest(filterContext)
Return
End If
Dim AuthCookie = filterContext.HttpContext.Request.Cookies(CookieName)
Dim AuthTicket = FormsAuthentication.Decrypt(AuthCookie.Value)
Dim Roles As String() = AuthTicket.UserData.Split(","c)
Dim UserIdentity = New GenericIdentity(AuthTicket.Name)
Dim UserPrincipal = New GenericPrincipal(UserIdentity, Roles)
filterContext.HttpContext.User = UserPrincipal
MyBase.OnAuthorization(filterContext)
End Sub
End Class
I've done this so I can use the roles parameter on the attribute, like this...
<Authorise(Roles:="Admin")>
This works perfectly on my pages that require authorisation. However, on my main page, which does not require authorisation (and therefore does not have the Authorise attribute) I would like to display different items depending on whether the user is (a) logged in and (b) whether they are an admin or not. For example...
#If HttpContext.Current.User.Identity.IsAuthenticated Then
' Display a welcome message (this works)
#If HttpContext.Current.User.IsInRole("Admin") Then
' Display a settings link (this does not work)
End If
End If
The "welcome message" part fires but the "settings link" part does not. This makes sense because this view does not have the Authorise attribute.
How can I check the IsInRole on pages that don't have the Authorise attribute?
I don't have a proper solution for this. Just a work around that may help before someone posts the proper solution.
I use, [Authorize] attribute for the actions but whenever I am in a partial view, I do a manual 'OnAuthorization'.
public class Authorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
....
}
public static void ManualOnAuthorization(HttpContext context)
{
if (context.User.Identity.IsAuthenticated && context.User.Identity.AuthenticationType == "Forms")
{
FormsIdentity fIdent = (FormsIdentity)context.User.Identity;
var user = new CustomUser(fIdent.Ticket.UserData);
var ci = new CustomIdentity(user);
var p = new CustomPrincipal(ci);
HttpContext.Current.User = p;
Thread.CurrentPrincipal = p;
}
}
}
I have put it in Authorize class and use it as following in a partial view.
#if(User.Identity.IsAuthenticated)
{
Authorize.ManualOnAuthorization(HttpContext.Current);
if (User.IsInRole("Admin"))
{
}
}
I need to write a function that help me do something in some of my Controllers so I decided to creat a class called Helper for that.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace HocVuiDiary.Helper
{
public class CookiesHelper
{
public void UpdateSubkey(string name, string subkey, string subvalue)
{
HttpCookie cookie;
if (Request.Cookies[name] == null)
{
cookie = new HttpCookie(name);
cookie[subkey] = subvalue;
}
else
{
cookie = Request.Cookies[name];
cookie[subkey] = subvalue;
}
cookie.Expires = DateTime.Now.AddDays(30);
Response.Cookies.Add(cookie);
}
}
}
The issue is I cannot Access to Request or Response any more!
PLease show me the right way!
You can use HttpContext.Current.Request and HttpContext.Current.Response in your helper class.
While the first answer is technically accurate, I am running into issues of inconsistency with creation of the cookie using an external .DLL. The code behind class calls the methods in the external .dll, the cookie is created, but after navigating to the next page the cookie does not exist, sometimes.
public void CreateCookie(string cookieName, string key, string value)
{
int minutes = 95;
string encryptedValue = utilities.EncryptString(value);
HttpCookie cookie = new HttpCookie(cookieName);
cookie[key] = encryptedValue;
cookie.Expires = DateTime.Now.AddMinutes(minutes);
HttpContext.Current.Response.Cookies.Add(cookie);
}
Other calls to the external class are working as expected.
public bool CheckCookieExists(string cookieName)
{
bool exists = true;
HttpCookie cookie = HttpContext.Current.Request.Cookies[cookieName];
if (cookie != null)
{
return exists;
}
return exists = false;
}
It's basically the same as accessing the session. Use httpcontext.current although it is frowned upon at times there is mention here of cleaning it up:
Can I use ASP.NET Session[] variable in an external DLL
Here you could define an interface like IRequest to abstract the specific implementation out but that's up to you.