0

I created an ASP.NET Core Web Api project with Visual Studio. It uses .NET 6.0, controllers and Swagger. The default structure is the below:

enter image description here

Right now I have two controllers more:

  • UsersController

    [ApiController]
    [Authorize] // <------- all endpoints are protected with cookie auth
    [Route("api/[controller]")]
    public class UsersController : ControllerBase { ... }
    

That class contains the below endpoint with a simple get operation:

/GET /api/users

[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> GetAll()
{
    var users = await _userService.GetAll();
    return Ok(users);
}
  • AuthenticationController

    [HttpPost]
    public async Task<IActionResult> AuthenticateAsync([FromBody] Credentials credentials) { ... }
    

/POST /api/authenticate

[AllowAnonymous] // <------ so we can call it with no cookie present in the request
[HttpPost]
public async Task<IActionResult> AuthenticateAsync([FromBody] Credentials credentials)
{
    var isAuthenticated = _cookieService.Authenticate(credentials.Username, credentials.Password);

    if (isAuthenticated)
    {
        var userId = _cookieService.CreateCookie(userData.Username, Request.HttpContext);
        var completeUserData = await _userService.GetById(userId);
        var partialUserData = _mapper.Map<PartialUserDataDto>(completeUserData );
    return Ok(partialUserData);
    }
    
    return Unauthorized();
}

_cookeService.Authenticate(...) just checks against an in memory dictionary if username and password exists (ex. { username: test1, password: pass1 }). partialUserData is an object containing some user fields to show them in the frontend (ex. username, role, email, etc.).

In Program.cs I use authentication and authorization middleware with cookies like this:

builder.Services.AddAuthentication(x =>
{
    x.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    x.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
    .AddCookie(x =>
{
    x.Cookie.SameSite = SameSiteMode.None;  // only for dev mode
    x.SlidingExpiration = true;
    x.Events = new CookieAuthenticationEvents
    {
        OnRedirectToLogin = redirectContext =>
        {
            redirectContext.HttpContext.Response.StatusCode = 401;
            return Task.CompletedTask;
        }
    };
});

...

app.UseCookiePolicy(new CookiePolicyOptions
    {
        Secure = CookieSecurePolicy.Always
    }
);

app.UseAuthentication();
app.UseAuthorization();
  1. The browser makes a /POST /authentication { username: test1, password: pass1 }
  2. The server checks whether the user exists in a dictionary in memory
  3. If it exists, it returns a cookie along some user data
  4. In my Angular frontend, I save that user data in a currentUser object that contains, among other data, the user role
  5. An auth guard renders the view based on the user role.
  6. I can naviagate to other views and make requests to the API because the browser sends automatically the cookie in step 3 to the server

My problem

  1. If I refresh the browser, I lose my currentUser object. I don't want to consider this a logout since I still have the cookie in the browser. But the user data was stored in memory, so obviously I lost it. Should I create a new protected endpoint like GET /user/<id> to get again the user data? Is it a good practice?

My goal is that, as long as I have the cookie in the browser, I consider the user is logged in so they don't need to enter their credentials in the login box each time they enter to the app or navigate to a different view. Also, as long I have the same cookie, the user can make any API request to the server. This is, I use the cookie for authentication and also for authorization.

I'm quite confused and don't know if this is a good approach.

chick3n0x07CC
  • 678
  • 2
  • 10
  • 30
  • have you tried HttpContext.User.Identity.IsAuthenticated? – Mo Haidar May 08 '23 at 12:03
  • How would that help me? – chick3n0x07CC May 08 '23 at 14:22
  • How about using [Identity](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity?view=aspnetcore-7.0&tabs=visual-studio#configure-identity-services)? [Controller](https://github.com/MintPlayer/MintPlayer/blob/master/MintPlayer.Web/Server/Controllers/Web/V3/AccountController.cs#L537-L544) - [Repository](https://github.com/MintPlayer/MintPlayer/blob/master/MintPlayer.Data/Repositories/AccountRepository.cs#L340-L344) - [AddIdentity](https://github.com/MintPlayer/MintPlayer/blob/master/MintPlayer.Data/Extensions/MintPlayerExtensions.cs#L31) – Pieterjan May 09 '23 at 07:39
  • @Pieterjan From the documentation I understand Identity is for Razor Pages, Blazor or MVC web apps, not pure web API's. So I don't know how to use Identity with my web API. – chick3n0x07CC May 09 '23 at 16:05
  • For mobile apps use Bearer authentication [like this](https://github.com/MintPlayer/MintPlayer/blob/master/MintPlayer.Web/Server/Controllers/Api/V1/AccountController.cs#L69-L75) – Pieterjan May 09 '23 at 17:14
  • For single page applications, [use cookie authentication + XSRF protection](https://medium.com/@pieterjandeclippel/asp-net-core-angular-xsrf-62c3833fd1fe) - [Here's a dotnet template](https://github.com/MintPlayer/MintPlayer.AspNetCore.Templates) – Pieterjan May 09 '23 at 17:23
  • Yes, I tried both cookies and JWT's but I'm still confused about if using cookies for both authentication and authorization along session management is a good practice. Assuming the client is a browser. – chick3n0x07CC May 09 '23 at 17:55

1 Answers1

1

If you want to check if user is still logged in, You can create an endpoint to check it. Here I create a endpoint to check the user:

[Route("api/[controller]")]
    [ApiController]
    public class CheckController : ControllerBase
    {
        [HttpGet]
        public bool islogin()
        {
            var result = HttpContext.User.Identity.IsAuthenticated;
            return result;
        }
    }

Then in the conmponent's constructor method in angular, I will send HttpClient to this endpoint to check if user is still logged in.

constructor(private fb: FormBuilder,private http: HttpClient){
      //......
      this.http.get('https://localhost:7239/api/Check',{ withCredentials: true }).subscribe((x)=>{
          if(x===true){
            this.IsloggedIn = "User has logged in"
          }else{
            this.IsloggedIn = "User has logged out"
          }
      })
      
    }

    IsloggedIn : string = "";

In the form submit method, I will also change the value of IsloggedIn

onSubmit() {
 this.http.post('https://localhost:7239/Login',this.contactForm.value,{withCredentials: true}).subscribe((x:any) =>{
    this.user.UserName = x.userName,
    this.user.PassWord = x.passWord,
    this.user.Age = x.age
    this.IsloggedIn = "User has logged in"
  })
}

Gif demo

enter image description here

Now if user refresh the browser, The data saved in currentUser object will be removed, Because of the httpclient in constructor, It will send request to the endpoint to check if user is still logged in. Then it show the information to remind user he is still logged in or not.

enter image description here

If you want to show the data saved in currentUser object after refresh, another method is to save this data in local storage after you get value from backend, refer to this link.

Xinran Shen
  • 8,416
  • 2
  • 3
  • 12
  • I appreciate the effort you put in your answer. One question though: what type of application are you running? Razor Pages, Blazor, MVC, Web API, etc.? – chick3n0x07CC May 09 '23 at 16:10
  • @chick3n0x07CC Asp.net core web api – Xinran Shen May 09 '23 at 16:46
  • @chick3n0x07CC Web Api use JWT token authentication in most case, but you can also use cookie authentication, refer to this [docs](https://www.blinkingcaret.com/2018/07/18/secure-an-asp-net-core-web-api-using-cookies/). – Xinran Shen May 09 '23 at 16:52
  • Who fills the value of `HttpContext.User.Identity.IsAuthenticated`? Is it the authentication middleware when it receives the cookie from the browser? – chick3n0x07CC May 09 '23 at 17:53
  • yes, When a user logs in and the server validates their credentials, the authentication middleware generates an authentication token (such as a cookie) and sends it to the client. On subsequent requests, the client includes this token in the request headers, and the authentication middleware verifies the token and sets the `HttpContext.User.Identity.IsAuthenticated` property to true if the token is valid. – Xinran Shen May 10 '23 at 05:42
  • Could you add the code where you configure the cookies middleware? Do you use any cookie service? In the backend side, I mean. Right now, I have a cookie implementation which returns OK or Unauthorized based on certain validation. But I don't use at all `HttpContext.User.Identity.IsAuthenticated`. – chick3n0x07CC May 10 '23 at 08:33
  • `HttpContext.User.Identity.IsAuthenticated` is in an individual endpoint, In my login endpoint, I also return OK or unauthorize. In your question. if the cookie has expired, `HttpContext.User.Identity.IsAuthenticated` will return false, It is a very simple way to check if user is still logged in. You can also write your method to check if user is still logged in. – Xinran Shen May 10 '23 at 08:58