THE SERVER
For the purposes of this demo app I'm going to add basic authentication using JSON web tokens. The majority of the server code is inspired by this blog series by Jon Hilton. Just to be clear though, you will need a more robust way of authenticating username and passwords. But as our focus is more on the Blazor side of things, this will be fine.
I'm going to start by adding a appsettings.json file to the project with the following key-value pairs.
{
"JwtSecurityKey": "RANDOM_KEY_MUST_NOT_BE_SHARED",
"JwtIssuer": "https://localhost",
"JwtExpiryInDays": 14
}
Then the following to the Startup.cs
This code is loading the key value pairs from the appsettings.json into a IConfiguration instance so they are available for use within the app.
Next I'm going to add a LoginController with a Login method, this where I'll submit the username and password to from the Blazor client.
Once again, hard-coding usernames and passwords this way is not good. In a real app I'd be checking against something like Azure AD or the like.
I'm not going to go into detail about what this code is doing. As I mentioned before, you can read all about it in Jon Hiltons blog. But to summarise, if the username and password match a valid JWT will be generated and returned to the caller. Otherwise, they will receive a 400 Bad Request.
I need to add a couple of items to the shared project, the LoginDetails class and a new login route to the Urls class.
Back in the Startup.cs I need to enable authentication and specifically JWT bearer authentication. First I'll add the following to the ConfigureServices method above the call to register the MVC services.
Then I'll add the following to the Configure method just above the app.UseMvc
statement.
I've now got my app setup to use JWT bearer authentication. All that's left to do is add the [Authorize]
attribute above any endpoints I want to require an authorised user. In the BlogPostController that is the AddBlogPost, UpdateBlogPost and DeleteBlogPost methods.
That concludes the changes needed in the server project, now let's move on to the client.
THE CLIENT
I'm going to start by adding a nuget package called BlazoredLocalStorage. This is a simple library I built to provide access to the browsers local storage APIs from Blazor. I'm going to be using it to store the JWT that comes from server after a successful login.
Next is the AppState class. This will contain the log in and log out methods as well as maintain if the user is logged in or not. This class will use the library above to save the auth token to local storage.
The code is pretty straightforward, the main work is being done in the Login method. It posts the login details up to the API, if it's successful then it saves the token to local storage, applies the authorization header to the HttpClient and marks the user as logged in.
I need to register AppState with the DI container. This is done in Startup.cs in the ConfigureServices method. I'll also add the services for BlazoredLocalStorage while I'm here as I forgot to do this earlier.
I can now track the logged in state of a user and I've got the ability to perform log in and log out requests. Now I'm going to make some changes to the MainLayout component to make use of some of that functionality.
First, I'm going to add a model class as currently I've only got the component markup defined. Up till now there hasn't been any logic required in this component. But as I've said previously, I prefer to keep the logic separated from the markup as much as possible.
Notice I'm inheriting from BlazorLayoutComponent here not the usual BlazorComponent. This is because BlazorLayoutComponent exposes a RenderFragment called Body which holds the content to be rendered in the layout.
Now I have access to AppState and I have a log out method on my layout component. I'm going to make some changes to the main menu.
As you can see I've added a check to see if the user is logged in, if they are then I'm showing the link to add a post. I'm also showing a button which allows the user to log out if they wish.
If the user isn't logged in then I'm showing a log in link which will send them to the log in component I'm going to build next.
I'm going to add a new folder called Login to the Features folder. Then add the following class, Login.cshtml.cs.
Then I'm going to add the component markup, Login.cshtml.
The component renders a simple form with username, password inputs. When a user attempts to login a call is made to the Login method on the AppStateclass. Once this is completed the IsLoggedIn property is checked on AppState. If this is true then the user is redirected to the home page and will now see the links available to authenticated users. If it's false, then a message is displayed stating the login attempt failed.
At this point things are looking pretty good! You should be able to fire up the app and log in, submit a post as well as be able to edit and delete it. Pretty cool, but there are a couple of little things I want to tidy up before I call it a day.
Currently a non-authenticated user can go directly to the add post component and while they will not be able to post anything this feels a bit clunky. I can check if the user is logged in in the OnInitAsync method. If they're not then I can simply redirect them back to the home page. Which should work nicely.
That's much better. The only other issue I have is the hard-coded author name. Now I have an authenticated user it would be good to use that name on posts. So I'm going to removed the hard-coded value from SavePost method on the PostEditorModel class. Then back in the server project I'm going to update the AddBlogPost method on the BlogPostsController to the following.
All done.
WRAPPING UP
In this post I've covered adding authorisation to my blogging application. A user can now log in and add new posts and edit or delete existing ones. This has been achieved by the use of JSON web tokens.
This post also marks the end of this series on building a blogging app with Blazor. I hope you've enjoyed reading them and have found something useful. As always, if you have any questions or comment then please let me know below. You can find all the code for this series on my GitHub.