Sunday, July 16, 2023

ASP.NET Core - Authentication and Authorization in Razor Page using Fetch API


In this tutorial, we will see a practical example of Authentication and Authorization in ASP.NET Core using Razor Page template with JavaScript fetch API.

If server side handler sends 401 or 403 status code then it has the following meaning.
  • 401 Unauthorized is the status code to return when the client provides no credentials or invalid credentials. 403 Forbidden is the status code to return when a client has valid credentials but not enough privileges to perform an action on a resource. The 401 (Unauthorized) status code indicates that the request has not been applied because it lacks valid authentication credentials for the target resource...The user agent may repeat the request with a new or replaced Authorization header field.
  • The 403 (Forbidden) status code indicates that the server understood the request but refuses to authorize it...If authentication credentials were provided in the request, the server considers them insufficient to grant access.

On the client side, JavaScript fetch API will receive this status code as response and we can implement the logic what to do then and thereafter. Remember that the server handler should return JSON result so that fetch API can deal with the response properly.

But the question is, how will server handler decide about the status code of 401 or 403. For this, we use user Role based authentication in this simple project.

Project

  • Open the Visual Studio. 
  • Create web project using ASP.NET Core Empty template.
  • Project name: AuthRP
  • Solution name: AuthRP
  • .NET Core version: 5.0
  • Add the AddRazorPages() into the IServiceCollection in Startup class.
  • Use MapRazorPages() as terminal middleware in Startup class.
Look at the below updated code in Startup class.
Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace AuthRP
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });
        }
    }
}
  • Add a new folder and rename it Pages.
  • Add a Razor page in Pages folder and name it Index. Two files are generated which are Index.cshtml and Index.cshtml.cs

User model and Index Page

Next step. Update the Index.cshtml.cs. We create a User class which is used as model data on the form.
Index.cshtml.cs


using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Threading.Tasks;

namespace AuthRP.Pages
{
    public class User
    {
        public string Username { get; set; }
        public string Password { get; set; }
    }
    public class IndexModel : PageModel
    {
        public User user { get; set; }
        public async Task<JsonResult> OnPostAuth(User user)
        {
            await Task.Delay(500);
            if (!ModelState.IsValid)
            {
                return new JsonResult("Some Error");
            }
            else
            {
                return new JsonResult($"Username is {user.Username} and Password is {user.Password}");
            }
        }
    }
}
Index.cs is the Razor Page which is designed for UI based on User model class.

@page
@model AuthRP.Pages.IndexModel
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
}
<h1 style="color:darkblue">Welcome to ABC Security Ltd. </h1>
<div>
    <h3 style="color:red">You must authenticate to access the Restricted page.</h3>
</div>
<div>
    <form method="post">
        <table>
            <tr>
                <td><label asp-for="user.Username"></label></td>
                <td><input type="text" asp-for="user.Username" /></td>
            </tr>
            <tr>
                <td><label asp-for="user.Password"></label></td>
                <td><input type="password" asp-for="user.Password" /></td>
            </tr>
            <tr>
                <td colspan="2" style="text-align:right"><input id="btnAuth" type="submit" value="Login"></td>
            </tr>
        </table>
    </form>
</div>
<div>
    <p id="result"></p>
</div>


<script type="text/javascript">
    (function () {
        var form1 = document.forms[0];
        btnAuth.addEventListener('click', async function (event) {
            let username = user_Username.value;
            let password = user_Password.value;
            if (username === 'Appliedk' && password == 'Appliedk2023') {
                // send the data for server side authentication
                var formdata = new FormData(form1);
                fetch("/?handler=Auth&user="+ formdata , // action is handler: OnPostAsync method
                    {
                        method: form1.method, // GET or POST, here it is POST
                        body: formdata
                    }
                )
                    // if any exceptions - log them
                    .catch(err => console.log("network error: " + err))
                    // promise success then response stream
                    .then(response => {
                        // read json from the response stream
                        // and display the data
                        // server returns JsonResult which is handled by JS
                        // using the json() method
                        response.json().then(data => {
                            console.log(data);
                            result.innerHTML = data
                        });
                    })
            }
            else {
                alert('fail')
            }
        })
    })();
</script>

FromForm attribute

The FromForm works in a HTTP post request if the multipart/form-data or x-www-url-encoded content type is used. The FromForm attribute is for incoming data from a submitted form sent by the content type application/x-www-url-formencoded while the FromBody will parse the model the default way, which in most cases are sent by the content type application/json , from the request body.

Right choice of enctype

If you have binary (non-alphanumeric) data (or a significantly sized payload) to transmit, use multipart/form-data. Otherwise, use application/x-www-form-urlencoded.
For application/x-www-form-urlencoded, the body of the HTTP message sent to the server is essentially one giant query string -- name/value pairs are separated by the ampersand, and names are separated from values by the equals symbol.

What is the difference between FromBody and FromUri?

The [FromUri] attribute is prefixed to the parameter to specify that the value should be read from the URI of the request, and the [FromBody] attribute is used to specify that the value should be read from the body of the request.

No comments:

Post a Comment

Hot Topics