3
\$\begingroup\$

I still have to implement API keys for client auth, but so far this is what I have for users. This was built using WebAPI and SimpleMembership with Forms Auth:

Is Authenticated:

public class AccountController : ApiController
{
public static DtoService _service = new DtoService();

// GET/api/isAuthenticated
[System.Web.Http.HttpGet]
public HttpResponseMessage IsAuthenticated()
{
    try
    {
        if (User.Identity.IsAuthenticated)
            return Request.CreateResponse(HttpStatusCode.OK, WebSecurity.GetUserId(User.Identity.Name));
        else
            return Request.CreateResponse(HttpStatusCode.OK, false);
    }
    catch (Exception e)
    {
        return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
    }

}

Login:

// POST /api/login
// [System.Web.Http.AllowAnonymous]
[System.Web.Http.HttpPost]
public HttpResponseMessage LogIn(LoginModel model)
{
    if (!ModelState.IsValid)
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
    try
    {
        if (User.Identity.IsAuthenticated)
            return Request.CreateResponse(HttpStatusCode.Conflict, "already logged in.");
        if (!WebSecurity.UserExists(model.UserName))
            return Request.CreateResponse(HttpStatusCode.BadRequest, "User does not exist.");
        if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
        {
            FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
            return Request.CreateResponse(HttpStatusCode.OK, "logged in successfully");
        }
        return Request.CreateResponse(HttpStatusCode.BadRequest, "Login Failed.");
    }
    catch (Exception e)
    {
        return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
    }
}

Log Out:

// POST /api/logout
[System.Web.Http.HttpPost]
////[ValidateAntiForgeryToken]
// [System.Web.Http.AllowAnonymous]
[Authorize]
public HttpResponseMessage LogOut()
{
    try
    {
        if (User.Identity.IsAuthenticated)
        {
            WebSecurity.Logout();
            return Request.CreateResponse(HttpStatusCode.OK, "logged out successfully.");
        }
        return Request.CreateResponse(HttpStatusCode.Conflict, "already done.");
    }
    catch (Exception e)
    {
        return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
    }
}

Register:

// POST: /api/register
[System.Web.Http.HttpPost]
//[ValidateAntiForgeryToken]
public HttpResponseMessage Register(RegisterModel model)
{
    if (!ModelState.IsValid)
    {
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
    }
    try
    {
        if (User.Identity.IsAuthenticated)
            return Request.CreateResponse(HttpStatusCode.Conflict, "User Already Registered and Logged In");
        if (WebSecurity.UserExists(model.UserName))
            return Request.CreateResponse(HttpStatusCode.Conflict, "User Already Registered");
        else
        {
            // Attempt to register the user
            WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
            WebSecurity.Login(model.UserName, model.Password);
            InitiateDatabaseForNewUser(WebSecurity.GetUserId(model.UserName));

            FormsAuthentication.SetAuthCookie(model.UserName, createPersistentCookie: false);
            return Request.CreateResponse(HttpStatusCode.Created, WebSecurity.GetUserId(model.UserName));
        }

    }
    catch (Exception e)
    {
        return Request.CreateResponse(HttpStatusCode.InternalServerError, e);
    }
}
\$\endgroup\$
3
  • \$\begingroup\$ I think you should not have your logout be a POST operation. \$\endgroup\$ Apr 24, 2013 at 14:59
  • \$\begingroup\$ "public static DtoService _service = new DtoService()". Is that a typo? \$\endgroup\$
    – dreza
    Apr 24, 2013 at 19:44
  • \$\begingroup\$ It's not; I just realized I don't need it though :p Why do you ask? \$\endgroup\$
    – RobVious
    Apr 24, 2013 at 19:50

3 Answers 3

5
\$\begingroup\$

Lets start with your URI's.

GET /api/isAuthenticated - This is not restful I guess. isAuthenticated doesn't sound like it is a resource, instead it sounds like it is a method returning true or false. May be it would help if you read more on Restful architecture.

POST /api/login - Even this sounds like a process but not resource

Instead I would go with /api/authentication URI

On GET it returns authenticated user information on POST you can send credentials to create a new authentication on DELETE you delete the authentication resource which means logout.

Above is just for example about how to use Restful URI's. To be blunt, login process should not be done through RESTful URI's as it introduces stateful system where as RESTful API should be stateless. Instead you should use authentication headers of HTTP for authenticating and authorizing a user.

I hope this helps you in understanding RESTful API.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for this. What would you suggest for Registration? POST /api/user? \$\endgroup\$
    – RobVious
    Apr 24, 2013 at 19:52
  • \$\begingroup\$ And - can you give an example of an api call that would log a user in without using restful URIs? I'm having a tough time imagining how this request would work. \$\endgroup\$
    – RobVious
    Apr 24, 2013 at 19:55
  • \$\begingroup\$ @RobVious Is it for Intranet or Internet? If it is Intranet then you can just make windows or basic auth. enabled. \$\endgroup\$
    – codebased
    Jul 24, 2014 at 12:15
2
\$\begingroup\$

For creating new record , please use Post with 201 return code. On successful update, return 200 (or 204 if not returning any content in the body) from a PUT.

To know more about status code please refer: http://en.wikipedia.org/wiki/Http_error_codes

IsAuthenticated, Login, Logout are Non-Resource Api Calls. Be sure that the user can tell it is a different type of operation Be pragmatic and make sure that these parts of your API are documented Don’t use them as an execute to build a RPC API

You can separate these by defining as [Route('api/helper/logout')].

Here is the reference document that I have prepared while developing my own web api. I have collected this guideline techniques from various sources and multiple books. http://www.doeaccian.com/restful-webapi-best-practices/

\$\endgroup\$
1
\$\begingroup\$

You have two responsibilities:

1) A LoginController, which is used to authenticate/log a user in

2) An UserController, which takes care of all actions belonging to the user

So a possible routing schema for the API would be:

(1) POST: api/login

Possible Answers: 200 OK or 401 Unauthorized

If an unauthorized user enters a page, where authorization is required, the answer should be: 403 Forbidden. The same goes for every REST-action, which needs authentication.

POST: api/logout

Possible Answers: 200 OK

(2) GET: api/users/

Possible Answers: 200 OK and a list of all users in some output format (JSON/XML whatever).

The client could specify his wishes via Accept e.g. application/json

GET: api/users/{ID}

Possible Answers: 200 OK including the requested user or 404 Not Found. If you keeping track of ex-users a possible answer could be 410 Gone.

POST: api/users/

Possible Answers: 201 Created including the created Ressource in some output format (JSON/XML whatever). If there is anything syntactically wrong (something missing or a field misspelled) with the userinput the answer is: 400 Bad Request

PUT: api/users/

Possible Answers: 204 No Content

DELETE: api/users/{ID}

Possible Answers: 204 No Content

So far a possible Layout. Now some words to your code:

    if (User.Identity.IsAuthenticated)
        return Request.CreateResponse(HttpStatusCode.OK, WebSecurity.GetUserId(User.Identity.Name));
    else
        return Request.CreateResponse(HttpStatusCode.OK, false);

If you read, what I wrote above, you understand, that this is misleading. You are branching on the condition User.Identity.IsAuthenticated and returning in both cases HttpStatusCode.OK: That makes no sense at all. Although the request itself is OK, the answer should be a 401.

The same goes for:

    if (User.Identity.IsAuthenticated)
        return Request.CreateResponse(HttpStatusCode.Conflict, "already logged in.");
    if (!WebSecurity.UserExists(model.UserName))
        return Request.CreateResponse(HttpStatusCode.BadRequest, "User does not exist.");
    if (WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
    {
        FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
        return Request.CreateResponse(HttpStatusCode.OK, "logged in successfully");
    }
    return Request.CreateResponse(HttpStatusCode.BadRequest, "Login Failed.");

If the user is already authenticated, there is no need to adress this case. You are responding with 409 Conflict which is wrong: there are no ressources conflicting. A reason to throw a 409 is: e.g. when you have a content management system with a versioning field, and a user wants to update with a version number which is lower than the current one; then that is a Conflict Or when you wrote

    if (WebSecurity.UserExists(model.UserName))
        return Request.CreateResponse(HttpStatusCode.Conflict, "User Already Registered");

That indeed is a conflict.

If the user wants to log in a hundred times: let him do that. Every time he does, he gets a valid token. End of story.

If a user doesn't exist, return a 404 Not Found and if the login failed return 401 Unauthorized. That's why they were invented: to signal, that a ressource isn't there and you are unauthorized to do that operation, you wanted.

And only, when there is something syntactically wrong with a request you should answer with 400 Bad Request. This is not about semantics.

So, if I am asking you: »Do you live on the moon?« this is a syntactically correct question. Your answer »Can you repeat the question? - I did not understand, what you meant.« is misleading.

That's why this

    if (!WebSecurity.UserExists(model.UserName))
        return Request.CreateResponse(HttpStatusCode.BadRequest, "User does not exist.");

is wrong.

If I ask you, whether you live on the moon, your answer should be yes or no.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.