I have implemented a DNN module that works fine. Now my employer wants me to expose some of the features as web services.I want to keep everything clean and simple so I decided to add an asmx web service to my module.
The web service itself was working fine but when I tried to add a HttpModule to handle request authentication everything was messed up.
I have an event handler for AuthenticateRequest as follow :
private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
{
var app = sender as HttpApplication;
if (app == null || !(app.Request.Url.ToString().EndsWith("WebServices.asmx"))) return;
var request = app.Request;
var authHeader = request.Headers["Authorization"];
if (authHeader != null)
{
var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);
// RFC 2617 sec 1.2, "scheme" name is case-insensitive
if (authHeaderVal.Scheme.Equals("basic",
StringComparison.OrdinalIgnoreCase) &&
authHeaderVal.Parameter != null)
{
AuthenticateUser(authHeaderVal.Parameter);
}
}
else
{
HttpContext.Current.Response.StatusCode = 401;
}
}
and here's the client side :
var service = new localhost.WebServices();
var credentialsCache = new CredentialCache
{
{new Uri(service.Url), "Basic", new NetworkCredential("user", "password")}
};
service.Credentials = credentialsCache;
Console.WriteLine(service.HelloWorld());
The problem is no Authorization header is added to my request this way. I searched and I found out that there was a round trip here that the header was not added for the first time and if response had a basic authorization header ,another request was sent with the header. Thus I added the else part :
else
{
HttpContext.Current.Response.StatusCode = 401;
}
and an EndRequest handler :
private static void OnApplicationEndRequest(object sender, EventArgs e)
{
var response = HttpContext.Current.Response;
if (response.StatusCode == 401)
{
response.Headers.Add("WWW-Authenticate",
string.Format("Basic realm=\"{0}\"", Realm));
}
}
Now every time I send a request I am redirected to login page !!!(I know that this happens because there is another FormAuthentication HttpModule that redirects the request to login page if StatusCode is 401)
Please tell me what am I doing wrong ?(is there a way to prevent the other HttpModule to interfere with my response ? )
UPDATE
I set PreAuthenticate property to true but still no authorization header is sent
Related
My problem: When a session expires user still can perform an action (search). Action results are some garbage (controller is not visited). I don't know why; I just want to redirect a user to login page.
My plan is to make custom Authorize and override HandleUnauthorizedRequest(HttpActionContext) and redirect a user to index.
I have no idea how to redirect to my default page.
Sample code:
public class SessionTimeoutAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
base.HandleUnauthorizedRequest(actionContext);
//redirect here
}
}
You're looking to set Response of actionContext to Unauthorized http response. Here's a sample how to do so.
public class SessionTimeoutAttribute: AuthorizeAttribute {
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext) {
base.HandleUnauthorizedRequest(actionContext);
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
This should hopefully redirect user to the page you (hopefully) defined in CookieAuthenticationOptions.
Edit: To be honest, this kind of defined the purpose of Web API if you let users access it in the same way as you would enter a normal page. You should on the client determine if response from the web api endpoint was success, unathorized etc. You should be returning that instead of direct redirect from the web api.
If you really wanted to redirect, you might try the following...
var response = actionContext.Request.CreateResponse(HttpStatusCode.Redirect);
response.Headers.Location = new Uri("https://www.stackoverflow.com");
actionContext.Response = response;
Again, I don't see the point in redirecting from Web API. It should only return the requested data/errors. You should be processing it else where.
I thought I can redirect to site directly from Web API... Well, I can't. Turned out I don't need custom Authorize, because [Authorize] redirect correctly. I need my client to redirect when Authorize wants. In my case (Angular 1,5) it was an interceptor.
app.service('httpRedirectInterceptor', ['$window', function ($window) {
this.response = function (redirection) {
if (typeof redirection.data === 'string') {
if (redirection.data.indexOf instanceof Function &&
redirection.data.indexOf('id="app-login-page"') != -1) {
$window.location.pathname = "/Account/Login";
}
}
return redirection;
};
this.request = function (req) {
var elem = angular.element(document.body).find('div[ncg-request-verification-token]').attr('ncg-request-verification-token');
req.headers['RequestVerificationToken'] = elem || "no request verification token";
return req;
};}]);
and in app.config
$httpProvider.interceptors.
push('httpRedirectInterceptor');
I am creating a WebApi Service that interacts with the Yahoo! API that uses OAuth 1.0. I am able to use MVC to call and get the access token back with no issue. I am experimenting in doing the process without using MVC at all. This is what I would like to occur.
Call Api through the browser (http://mysite/api/auth/AuthentiateWithYahoo)
New tab is opened up to auth page, access is given.
Redirect is done to another method on the controller and the access token is stored.
Here is the code that currently works with MVC.
public ActionResult AuthenticateWithYahoo()
{
var callbackUri = new Uri ("http://localhost/yahoo/api/auth/Home/YahooOAuthCallback");
try
{
//this call eventually does this
//var request = this.Consumer.PrepareRequestUserAuthorization(callback, null, null);
//this.Consumer.Channel.Respond(request);
this.Fantasizer.BeginAuthorization(callbackUri);
}
catch (ProtocolException pe)
{
var webException = pe.InnerException as WebException;
if (webException != null)
{
HttpWebResponse response = webException.Response as HttpWebResponse;
if (response != null && response.StatusCode == HttpStatusCode.Unauthorized)
{
var appException =
new ApplicationException(
"Unable to authorize with Yahoo. Check YahooConsumerKey and YahooConsumerSecret environment variables.",
pe);
throw appException;
}
}
throw;
}
// This will not get hit
return null;
}
public ActionResult YahooOAuthCallback()
{
// this ends up calling OAUTH complete authorization
this.Fantasizer.CompleteAuthorization();
return RedirectToAction("ListLeagues", "User");
}
What would I need to do to move this process to one that is not dependent on MVC? If possible, passing the access token back in the call would be ideal. Let me know if more information is required.
I've writing some tests on ServiceStack services that require authentication using a custom CredentialAuthenticationProvider.
If I create a test by not authenticating with servicestack, I get a serialization error instead of a 401 error. Serialization error is because the service is redirecting to the MVC login HTML page.
How can I prevent a redirect to MVC when the call is on the /api/ path for serviceStack services so that the service returns a 401 instead?
[TestMethod]
public void DeleteUser_not_authenticated()
{
var client = new JsonServiceClient(STR_APIURL);
var resp1 = client.Post(new Auth() { UserName = "user", Password = "***" });
Assert.IsTrue(resp1.UserName == "user");
var user = client.Get(new GetSupportUser { Email = "test#gmail.com" });
client.Post(new Auth { provider = "logout" });
try
{
var resp = client.Delete(user);
}
catch (HttpException e)
{
Assert.IsTrue(e.GetHttpCode() == 401);
}
}
Edit
Per Mythz suggestion, I tried this in global.aspx, but this didn't stop the redirect:
protected void Application_EndRequest(object src, EventArgs e)
{
ServiceStack.MiniProfiler.Profiler.Stop();
var ctx = (HttpApplication)src;
if (ctx.Request.Path.Contains("/api/"))
ctx.Response.SuppressFormsAuthenticationRedirect = true;
}
If you're using .NET 4.5, you can disable built-in authentication redirect
by setting HttpResponse.SuppressFormsAuthenticationRedirect=true which you can add in your Global.asax.cs:
protected void Application_EndRequest(Object sender, EventArgs e)
{
var ctx = (HttpApplication)sender;
var suppressForPath = ctx.Request.PathInfo.StartsWith("/api");
ctx.Response.SuppressFormsAuthenticationRedirect = suppressForPath;
}
For earlier versions of ASP.NET see ServiceStacks docs on Form Hijacking Prevention for registering a custom SuppressFormsAuthenticationRedirectModule IHtptModule to prevent this.
I changed #mythz answer to the Begin_Request instead of End_Request and it is now preventing redirect to forms authentication and returning 401 response.
protected void Application_BeginRequest(object src, EventArgs e)
{
var ctx = (HttpApplication) src;
var suppressForPath =ctx.Request.Path.StartsWith("/api");
ctx.Response.SuppressFormsAuthenticationRedirect = suppressForPath;
}
I have setup a basic authentication ActionFilterAttribute in my MVC web site to lock it down while in development, which works 100% across all the browsers that I am testing for (IE9+, Chrome, FF, iOS Safari) but when I load up Chrome in Android 4.0, it simply displays a 401 access denied and NEVER asks me for the basic authentication credentials?
This is my code for the OnAuthorization method:
public void OnAuthorization(AuthorizationContext filterContext)
{
var controllerName = (filterContext.RouteData.Values["controller"] as string).ToLower();
if (_controllersToIgnore.Contains(controllerName))
{
return;
}
bool credentialsMatch = false;
var req = filterContext.HttpContext.Request;
if (!string.IsNullOrEmpty(req.Headers["Authorization"]))
{
var cred = System.Text.Encoding.ASCII.GetString(Convert.FromBase64String(req.Headers["Authorization"].Substring(6))).Split(':');
var user = new { Name = cred[0], Pass = cred[1] };
if (user.Name == Username && user.Pass == Password)
{
credentialsMatch = true;
}
}
if (!credentialsMatch)
{
var res = filterContext.HttpContext.Response;
res.StatusCode = 401;
res.AddHeader("WWW-Authenticate", "Basic realm=\"\"");
res.End();
filterContext.Result = new EmptyResult();
}
}
This problem went away on its own strangely enough, I did nothing (apparent) and it just stopped occurring.
I've spent some time over the last few days trying to implement a feature for my web application. The feature should add new events to a users google calendar while they are offline. I read the Google OAuth2 documentation for web server applications and seem to understand the basics of it. I created a link to authorize the application for offline access:
<a target='_blank' href='https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Ftasks&response_type=code&client_id=<MY CLIENT ID>&access_type=offline&redirect_uri=http%3A%2F%2Flocalhost:49949%2Foauth2callback.aspx'>Grant Tasks Permission</a>
If the user accepts then I capture the refresh token at the redirect uri like this:
private static OAuth2Authenticator<WebServerClient> _authenticator;
protected void Page_Load(object sender, EventArgs e)
{
if (HttpContext.Current.Request["code"] != null)
{
_authenticator = CreateAuthenticator();
_authenticator.LoadAccessToken();
}
Response.Write("Refresh Token: " + _authenticator.State.RefreshToken);
}
private OAuth2Authenticator<WebServerClient> CreateAuthenticator()
{
var provider = new WebServerClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = "<MY CLIENT ID>";
provider.ClientSecret = "<MY CLIENT SECRET>";
return new OAuth2Authenticator<WebServerClient>(provider, GetAuthorization);
}
private IAuthorizationState GetAuthorization(WebServerClient client)
{
return client.ProcessUserAuthorization(new HttpRequestInfo(HttpContext.Current.Request));
}
For testing purposes I have been copying the refresh token to a text file for further use.
My problem is using this refresh token for offine access. I have been using this code to refresh the token:
protected void btnGetTasks_Click(object sender, EventArgs e)
{
if (_service == null)
{
_authenticator = CreateAuthenticator();
_service = new TasksService(new BaseClientService.Initializer() { Authenticator = _authenticator });
}
var cl = _service.Tasklists.List().Fetch();
}
private OAuth2Authenticator<WebServerClient> CreateAuthenticator()
{
// Register the authenticator.
var provider = new WebServerClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = "<MY CLIENT ID>";
provider.ClientSecret = "<MY CLIENT SECRET>";
var authenticator = new OAuth2Authenticator<WebServerClient>(provider, GetAuthorization);
return authenticator;
}
private IAuthorizationState GetAuthorization(WebServerClient client)
{
string scope = "https://www.googleapis.com/auth/tasks";
IAuthorizationState state = new AuthorizationState(new[] { scope });
state.RefreshToken = "<REFRESH TOKEN FROM FIRST STEP>";
var result = client.RefreshToken(state);
return client.ProcessUserAuthorization();
}
Everything seems fine at this point. When I step through the code I can see the result from client.RefreshToken(state) is true. The issue is when I call this line of code:
_service.Tasklists.List().Fetch();
It returns a (401) unauthorized error from google. I'm looking into the cause but I am not sure how to proceed and I am running short on time with this feature. Any advice would be greatly appreciated. Thanks!
Seems just the act of putting code on here always helps me figure it out a little sooner :)
It now appears this line is unnecessary:
return client.ProcessUserAuthorization();
removing that from the GetAuthorization method and just returning the state passed to RefreshToken has resolved the unauthorized error. I'll leave the question in case it stumps anyone else.