I have created 2 very simple methods:
[Authorize]
[HttpGet]
public string getUser()
{
return User.Identity.Name;
}
[HttpPost]
public bool SignIn(Credentials cred)
{
var user = userRepository.ValidateUser(cred);
if (user != null)
{
if (user.IsActive)
{
FormsAuthentication.SetAuthCookie(userRepository.GetUserIdByEmail(cred.Email).ToString(), cred.RememberMe);
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
user.UserId.ToString(),
DateTime.UtcNow,
DateTime.UtcNow.AddDays(Convert.ToInt32(ConfigurationManager.AppSettings["CookieTimeoutInDays"])),
true,
"MyTicket",
FormsAuthentication.FormsCookiePath);
//Encrypt the ticket.
string encTicket = FormsAuthentication.Encrypt(ticket);
//Create the cookie.
HttpCookie mycookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// Set the cookie's expiration time to the tickets expiration time
if (ticket.IsPersistent)
mycookie.Expires = ticket.Expiration;
Response.AddHeader(FormsAuthentication.FormsCookieName, encTicket);
return true;
}
else
return false;
}
else
{
return false;
}
}
I put these functions in an API controller and a normal controller (the only line thats different is HttpContext.Current.Response.AddHeader(FormsAuthentication.FormsCookieName, encTicket); when its in the api controller). When I authenticate with the normal controller and pass the same cookie back to call getUser() it works, but when I do it to the API controller it does not work..I am using mobile devices to call both of these controllers, not a browser. Now I understand API controller usually uses basic authentication by passing in username and password in the headers in each call, but is there anything wrong with doing it from a normal controller? What are the advantages of using the asp.net web API over just a normal controller?
When I authenticate with the normal controller and pass the same cookie back to call getUser() it works, but when I do it to the API controller it does not work
I'm not sure why that's the case. If you're using the normal templates, note that your ApiController will live under an /api route, and note that the action name won't be part of the URL. If your WebApiConfig says:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
and your controller class is named FooController, then your ApiController URLs will be something like:
http://:33504/api/Foo
(for both the GET and the POST).
Using cookies with Web API generally isn't the best approach, but it is possible if you have a particular need for it.
There are a couple of other places you might be getting tripped up:
You're generating a forms auth cookie twice. The SetAuthCookie line is doing it once, and putting it in a cookie header. Then, the Response.AddHeader is doing it again, putting it in a custom header (not a cookie header).
When you say Response.AddHeader(FormsCookieName) I think you meant: Response.SetCookie(myCookie). Your current code is adding a custom header named FormsCookieName; it's not adding a cookie (in the Set-Cookie header) with that cookie name.
With Web API, using HttpContext.Current.Response isn't generally recommended. Instead, consider returning HttpResponseMessage and setting the header properties on that object.
Now I understand API controller usually uses basic authentication by passing in username and password in the headers in each call, but is there anything wrong with doing it from a normal controller?
When you use Web API, you're generally doing REST and cookies don't fit very well with the hypermedia ideas there. If you're not doing hypermedia/REST, then I suppose you could use cookies, though again it generally isn't the best fit.
What are the advantages of using the asp.net web API over just a normal controller?
Web API gives you a self-host story and the ability to do content negotiation and a nice HTTP programming model. MVC was more designed around HTML specifically (not other content types). If you're returning HTML, MVC probably makes sense. If nothing in your app returns HTML, Web API is probably a better fit.
For this one controller, though, I'd do whatever you do in the rest of your app. (I wouldn't pick a different framework for just this one controller.)
Related
I'm using React router for my SPA and ASP.Net Core as an API backend using Identity for authentication. At the moment I'm trying to add email confirmation as part of the user registration process. What's troubling me is how to generate the confirmation URL without hard-coding the URL path into the backend.
This is what I'm currently working with:
// Somewhere in my UserService.cs...
// '_urlHelper' is an `IUrlHelper` injected into my service
var routeUrl = _urlHelper.RouteUrl("ConfirmEmail_Route",
new EmailConfirmationRequest { Email = email, Token = token },
requestScheme);
// Send the URL in some nicely formatted email
await _emailSender.SendConfirmationEmail(email, routeUrl);
// My API controller action to handle email confirmation
[HttpPost(Name = "ConfirmEmail_Route")]
public async Task<ActionResult> ConfirmEmail([FromBody] EmailConfirmationRequest payload)
{
var response = await _userService.ConfirmEmail(payload.Token, payload.Email);
...
}
The trouble is, that this generates a URL with a path like "/api/auth/ConfirmEmail?Email=..." but my React route is configured to handle a path like "/ConfirmEmail?Email=...". This means, when opening the URL the browser is obviously reaching the API controller action directly rather than going through my SPA (ignoring the fact the action expects a POST request).
All this makes good sense because _urlHelper.RouteUrl(...) only sees the controller actions within ASP.Net Core itself and knows nothing about the routes React uses. What I could do is hard-coding it somewhat like this:
var routeUrl = $"{requestScheme}://{hostname}/ConfirmEmail?Email={email}&Token={token}";
... which is not very versatile (I need to consider how to handle port number, subdomains etc.).
Are there any good alternatives I haven't been able to find yet?
Edit 26/12/2020:
It seems there's a little confusion about what the roles of my SPA and API backend are. To elaborate, this is my setup in Startup.cs (using .Net Core 2.1):
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Other irrelevant setup left out for brevity
// ...
app.UseAuthentication();
// Setup routing
// Specific routes are defined in controllers
app.UseMvc();
app.MapWhen(ctx => ctx.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseStatusCodePagesWithReExecute("/api/error/{0}");
});
app.MapWhen(ctx => !ctx.Request.Path.Value.StartsWith("/api"), builder =>
{
builder.UseStatusCodePagesWithReExecute("/error/{0}");
builder.UseMvc(routes =>
{
// For any path not beginning with "/api" return the SPA (Javascript) bundle
routes.MapSpaFallbackRoute("spa-fallback", defaults: new { controller = "Home", action = "Index" });
});
});
}
In other words: the REST API is supposed to be seen as an individual entity not caring about rendering a view but only exposing the functionality communicating in JSON. The React SPA is a bundled Javascript file that takes care of all the UI rendering and communicating with the REST API for signup, login and whatnot. All is secured using JWT (tokens) for authentication.
This means that the REST API does not care about the paths/URLs that the SPA uses to navigate the user through the application (at least as far as possible I'd like the API to be agnostic about how a client/SPA handles the URLs/UI/navigation - but in this case I might make an exception if necessary). So, there's no controller actions in the backend matching the routes used in the React routes SPA, which makes it difficult to _urlHelper.RouteUrl(...) but I'm open for suggestions.
According you comment, "My API backend doesn't really know about the routes the SPA uses".
What I think is you can ask frontend to pass the URL what they want when click the link in email.
User register will make a POST request to backend with
{
account: xxx
confirmEmailUrl: "https://www.example/confirmEmail"
// Front-end pass what URL they want and create the corresponding page on their side
// So they need to create the page by themselves
}
Send email
var url = "https://www.example/confirmEmail" + userId
SendEmail(url)
So when user get email and click the link in email, it will redirect to the corresponding page created by frontend and you don't need to know any about frontend
3.Call the confirm API in frontend.
They need to implement this by themselves.
In the confirm page they created when page loaded.
Get userId from query string and call the API you provide
I am new to RESTful services using WebApi. I have a front-end web application that uses FormsAuthentication to authenticate users. I am able to use the User.Identity property without any problems in my MVC controller methods.
However, I want to use Angular to make Ajax calls from the browser to the Restful methods in WebApi. The problem occurs with the user principal in these methods - HttpRequestMessage.GetUserIdentity() always returns null. By contrast, Thread.CurrentPrincipal in these methods correctly returns the currently authenticated user identity. My WebApi controller is decorated with the Authorize attribute.
What am I missing that is stopping GetUserIdentity() from working? Here is my controller.
[Authorize]
public class CategoryController : ApiController
{
public IEnumerable<ICategoryJson> Get(HttpRequestMessage request)
{
var user = request.GetUserPrincipal(); // returns null
var user1 = System.Threading.Thread.CurrentPrincipal; // returns authenticated user identity
return null;
}
}
And here is my Ajax call.
$http.get("/api/Category", config).then(function (response) {
Array.prototype.push.apply(service.list, response.data);
service.listLoading = false;
});
MVC controller inherits from a different base class so that's why it works in the MVC controller and not the Web API.
In Web API 2 you can use RequestContext.Principal or as you have used the Thread.CurrentPrincipal within your controller action to get the users Identity.
I don't think this issue is related to the ajax or angular call. you can try calling the same MVC controller action from the angular code and it should still return the user's identity.
I am trying to implement authentication for my web api.
I have read about different techniques of api authentication and the token technique is the most reasonable for me.
I read different articles about how to implement token based authentication in asp.net but they all rely on different libraries such as OAuth or Owin which also provide their own method of database interactions.
The thing is that I have already implemented database interaction with abstract repositories and entities and I would like to find out how can I implement api authentication easily and simply without interfering with my current design.
(By the way, my project is built on top of an empty web api project, so it doesn't come with all the bootstrap and authentication classes).
Thank you
One solution I've seen is to use .NET's HttpApplicationState class and store tokens in appstate; this way you're not directly messing with Session (which would be a REST antipattern), but you can still track all currently logged in users and use HttpContext/HttpActionContext to x-ref active tokens in the app. The benefit to using HttpActionContext is that it is thread-safe, whereas HttpContext is not, so you can lock the appstate, mess with the HttpContext of an individual request, and then unlock the appstate to allow other threads in.
Since locking/unlocking appstate does tie up the app, I'm not sure how well this solution scales, but here it is anyway . . .
General outline:
When a user first logs in, a token is generated for him/her and stored in appstate. Then you can tag any API calls that require authentication (or that need other information stored on that user) with a custom attribute that checks for that token in the appstate, sending the token name as a header in the API call (e.g. "{token-name: TOKEN}").
Here's a brief example:
[in Controller method first activated at login:]
CustomUserObject user = new CustomUserObject();
//store user props
string token = Guid.NewGuid().ToString();
//create AppState instance, mine's called _appState
//...
_appState.Lock();
_appState[token] = user;
_appState.UnLock();
//...
[Then in global.asax:]
public class CustomAuthorize : System.Web.Http.AuthorizeAttribute
{
HttpRequestMessage request = actionContext.ControllerContext.Request;
string token = string.Empty;
if (request.Headers.GetValues("token-name") != null)
{
token = request.Headers.GetValues("token-name").FirstOrDefault().ToString();
IAppStateService appService; //<--- I've created a custom service tier class for appstate stuff
//Get appState instance, however makes sense for you.
//I'm using repo pattern with UnitOfWork, so mine looks like this...
//"IContainer ioc = DependencyResolution.IoC.Initialize();"
//"IAppStateService appService = ioc.GetInstance<IAppStateService>();"
appService.SetHttpApplicationState(HttpContext.Current.Application);
bool isAuthorized = appService.CheckTokenAndDoStuff(token);
//inside that method ^^^ you'll do stuff like
//"_appState.Lock();"
//"if (_appState[token] == null) return false" (or whatever)
//"_appState.Unlock();"
}
if (isAuthorized)
{
HttpResponseMessage resp = request.CreateResponse(HttpStatusCode.OK);
resp.Headers.Add("AuthenticationToken", token);
resp.Headers.Add("WWW-Authenticate", "Basic");
resp.Headers.Add("AuthenticationStatus", "Authorized");
}
return isAuthorized;
}
[then in webapi]
[HttpPost]
[CustomAuthorize]
public HttpResponseMessage NameOfMethod(...)...
...and that should x-check your appstate for your user token for you. Just make sure to include your token in your request header, and make sure to include the Basic Auth info in your response header.
I am using a WebAPI service in my webapplication. In this service is used for all account functions (/api/account/login, /api/account/logout, ...). Within the same webroot I have a website which uses this webAPI service to communicate with the backend system. So from my C# code i'm calling the IsLoggedIn function in my WebAPI which returns true when I'm logged in. This is working great.
[HttpGet]
public bool IsLoggedIn()
{
return (WebSecurity.IsAuthenticated);
}
In my arearegistration i added the following code:
context.Routes.MapHttpRoute("Account", "api/account/{action}/{id}", new { Controller = "Account", id = RouteParameter.Optional });
And the follwing GlobalConfiguration:
GlobalConfiguration.Configuration.Filters.Add(new ExceptionHandlingAttribute());
GlobalConfiguration.Configuration.Formatters.Clear();
GlobalConfiguration.Configuration.Formatters.Add(new JsonMediaTypeFormatter());
The custom ExceptionHandlingAttribute checks if the thrown exception is of a specific type and returns a custom ReasonPhrase, so nothing special.
When I'm logged in and call the function from javascript (jQuery) or in Fiddler the IsLoggedIn function returns false. What should I add to my jQuery call to make sure the the right user is still found in the WebAPI? This is happening for POST and GET calls.
Please help :)
It will always be false as REST is stateless, each request knows nothing about previous requests.
You could enable Session State by doing something like this
http://www.strathweb.com/2012/11/adding-session-support-to-asp-net-web-api/
A better approach would be to have your Login call return a token which is then passed on subsequent calls to identify the user. Here's an example:
How to use OAuth 2 - OAuth 2 C# example
I'm having problem with getting ServiceStack [Authentication] attribute to work in ASP.Net MVC4 controller, pages / action methods with the attribute keep redirecting Users to the login page even after the login details are submitted correctly.
I've followed the SocialBootstrapApi example, with the difference being that all the authentication web service calls are made from the controllers:
this.CreateRestClient().Post<RegistrationResponse>("/register", model);
Other things that I've done so far:
Use my own user session implementation subclassing AuthUserSession (not too different from the example, but using my own implementation of User table)
Inherit ServiceStackController on my BaseController, overriding the default login URL
Enable Auth feature in AppHost with my user session implementation
Registration does work, user auth logic works (even though the session does not persist), and I can see the ss-id and ss-pid cookies in the request.
So my complete list of questions:
How do I make the [Authenticate] attribute work (or, what did I do wrong)?
How do I save and reuse the user session in an MVC controller? At the moment this.UserSession is always null.
How do I logout a user? this.CreateRestClient().Get<AuthResponse>("/auth/logout"); does not seem to work.
Update 1:
The session cookies (ss-id and ss-pid) gets created when I attempt to load the secured page (ones with [Authenticate] attribute), before any credentials get submitted. Is this the expected behaviour?
Update 2:
I can see that the session is saved in MemoryCacheClient, however trying to retrieve it in the base controller via this.Cache.Get<CustomUserSession>(SessionKey) returns null (where SessionKey is like: urn:iauthsession:1)
After much fiddling around, apparently the way to hook ServiceStack authentication is to call the AuthService via:
try {
authResponse = AuthService.Authenticate(new Auth{ UserName = model.UserName, Continue = returnUrl, Password = model.Password });
} catch (Exception ex) {
// Cut for brevity...
}
and NOT authResponse = this.CreateRestClient().Post<AuthResponse>("/auth/credentials", model);!
Where AuthService is defined in the base controller as:
public AuthService AuthService
{
get
{
var authService = ServiceStack.WebHost.Endpoints.AppHostBase.Instance.Container.Resolve<AuthService>();
authService.RequestContext = new HttpRequestContext(
System.Web.HttpContext.Current.Request.ToRequest(),
System.Web.HttpContext.Current.Response.ToResponse(),
null);
return authService;
}
}
Everything else (incl. session) works correctly now.
You can find how it could be done in the ServiceStack Use Cases repository. The following example is based on MVC4 but works perfectly for MVC3 either: CustomAuthenticationMvc.