Home > Software design >  Blazor WASM Authentication and Authorization on Components and Controllers
Blazor WASM Authentication and Authorization on Components and Controllers

Time:01-15

I am developing a Blazor WASM with authentication and authorization. The idea is that the user need to login in order to be able to view the Components of the Client Project but also to be able to consume data of Controllers from Server Project which are behind the /api.

Currently I have implemented the restriction on Client components:

<AuthorizeView>
    <NotAuthorized>
        <div >
            <div >
                <p>Please sign in to use the Platform...</p>
            </div>
        </div>
    </NotAuthorized>
    <Authorized>
        @Body
    </Authorized>
</AuthorizeView>

I have also a Login and a Logout Page which are storing a Cookie for later use and perform a custom AuthenticationStateProvider

await LocalStorage.SetItemAsync<int>($"{Parameters.application}_{Parameters.enviroment}_userid", authentication.user_id);
await LocalStorage.SetItemAsync<string>($"{Parameters.application}_{Parameters.enviroment}_username", authentication.user_name);
await AuthStateProvider.GetAuthenticationStateAsync();

The AuthenticationStateProvider code is the following:

public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
    var state = new AuthenticationState(new ClaimsPrincipal());

    string authcookie_name = $"{Parameters.application}_{Parameters.enviroment}_username";
    string authcookie_value = await _localStorage.GetItemAsStringAsync(authcookie_name);
    if (!string.IsNullOrEmpty(authcookie_value))
    {
        var identity = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.Authentication, authcookie_value)
        }, "Login");

        state = new AuthenticationState(new ClaimsPrincipal(identity));
    }

    NotifyAuthenticationStateChanged(Task.FromResult(state));

    return state;
}

The authentication controller is the following:

[HttpPost, Route("/api/auth/login")]
public IActionResult AuthLogin(Authentication authentication)
{
    try
    {
        int auth = _IAuth.AuthLogin(authentication);
        if (auth != -1)
        {
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Authentication, authentication.user_name)
            };
            var claimsIdentity = new ClaimsIdentity(claims, "Login");

            var properties = new AuthenticationProperties()
            {
                IsPersistent = true
            };

            HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), properties);
        }

        return Ok(auth);
    }
    catch { throw; }
}

Everything is working as excepted and the user need to login in order to see the content of the pages, but he is able to see the data of each page if he perform an http call enter image description here

I have no clue why and when this is happening. Then if user Logout and Login again it works for a while again.

===============UPDATE 1===============

After lot of investigation, seems that the client side is authenticated and then every time it sees the localstorage item it continues to be in authenticated state. On the other side the server state is based on a cookie which expires after 30mins.

enter image description here

So the Client and the Server states are operated differently and that's why the Client seems authenticated while Server is not while denying access on controllers.

I think the solution is to change the CustomAuthenticationStateProvider in order to check if the cookie exists and if it's valid. So the event order be as follow:

User SingIn via Client Page -> Server Controller creates the cookie -> Client Page is authenticated via Authentication State Provider which reads the cookie.

Any ideas?

CodePudding user response:

Seems that is possible to read and write cookies from Client Project only via Javascript. What needs to be done is the following:

A custom javascript file "cookie.js", under wwwroot/js:

export function get() {
    return document.cookie;
}

export function set(key, value) {
    document.cookie = `${key}=${value}`;
}

A C# class file "CookieStorageAccessor.cs", under /Classes:

public class CookieStorageAccessor
{
    private Lazy<IJSObjectReference> _accessorJsRef = new();
    private readonly IJSRuntime _jsRuntime;

    public CookieStorageAccessor(IJSRuntime jsRuntime)
    {
        _jsRuntime = jsRuntime;
    }

    private async Task WaitForReference()
    {
        if (_accessorJsRef.IsValueCreated is false)
        {
            _accessorJsRef = new(await _jsRuntime.InvokeAsync<IJSObjectReference>("import", "/js/cookie.js"));
        }
    }

    public async ValueTask DisposeAsync()
    {
        if (_accessorJsRef.IsValueCreated)
        {
            await _accessorJsRef.Value.DisposeAsync();
        }
    }

    public async Task<T> GetValueAsync<T>(string key)
    {
        await WaitForReference();
        var result = await _accessorJsRef.Value.InvokeAsync<T>("get", key);

        return result;
    }

    public async Task SetValueAsync<T>(string key, T value)
    {
        await WaitForReference();
        await _accessorJsRef.Value.InvokeVoidAsync("set", key, value);
    }
}

The C# class can be used injecting javascript and reading the cookie on

CustomAuthStateProvider:
//CREATE INSTANCE OF COOKIE ACCESSOR
CookieStorageAccessor cookieStorageAccessor = new CookieStorageAccessor(_jSRuntime);

//CHECK IF COOKIE IS EXISTS FROM COOKIE ACCESSOR
string auth_cookie = await cookieStorageAccessor.GetValueAsync<string>("authentication");
if (!string.IsNullOrEmpty(auth_cookie))
{ }
else
{ }
  • Related