Just let me login! Going Password less with .NET

arjavdave

Arjav Dave

Posted on April 6, 2021

Just let me login! Going Password less with .NET

Passwords are a pain in the a..

With thousand's of softwares and app's launching everyday, you would like to make your software stand out. Most importantly, it should have some unique USP, but in addition it should provide convenience and ease of use.

For instance, one of the pain points for many apps is they require a username and a password to login. I personally have to remember 10-15 passwords for apps like Gmail, Facebook, Instagram, etc. You get the idea.

In today's article we are going to create a solution for your API's that will allow your users to login without a password.

The How of Going Password less

In order to omit the password there needs to be some type of token generated for a user.

This token will then be sent to a user where only the user can access it e.g. on email or phone number. Here is an overview of the flow.

No Password Login Flow

Dotnet Identity provides ways to generate tokens for email confirmation or changing email or phone. We will see more about it below.

There are mainly two token providers available

  • TotpSecurityStampBasedTokenProvider (Time-based One Time Password).
  • DataProtectionTokenProvider

TotpSecurityStampBasedTokenProvider

It generates time based tokens which are valid for around 3 minutes (Reference: Source Code). Based on the token provider the tokens are generated from the Email, PhoneNumber or user's id as well as the user's security stamp.

Dotnet Identity provides utility classes EmailTokenProvider and PhoneNumberTokenProvider that are subclasses of TotpSecurityStampBasedTokenProvider.

DataProtectorTokenProvider

If you want to generate a token that doesn't expire for a long duration DataProtectorTokenProvider is the way to go.

DataProtectorTokenProvider generates tokens using a DataProtector and cryptographic algorithms. You can check out the implementation for more details here.

In this article we are going to subclass DataProtectorTokenProvider so that our token is valid for 10 minutes.

Setting up Identity

The .NET Identity provides ways to manage users, passwords, profile data, roles, claims, tokens, email confirmation, and more.

Let's start with a scratch project. Create a new project by executing the command dotnet new webapi –-name NoPasswordProject.

dotnet add package Microsoft.EntityFrameworkCore.InMemory --version 5.0.4
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore --version 5.0.4
Enter fullscreen mode Exit fullscreen mode

We are going to create an in memory database for this tutorial. But you can use a database of your choice and accordingly change the package above.

Note: The in memory database will clear the users every time the server restarts.

Custom Token Provider

Let's create a custom token provider that generates token that are valid for 10 minutes.

NPTokenProvider

Create a new file called NPTokenProvider.cs. NP prefix stands for No Password.

using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

public class NPTokenProvider<TUser> : DataProtectorTokenProvider<TUser>
where TUser : IdentityUser
{
    public NPTokenProvider(
        IDataProtectionProvider dataProtectionProvider,
        IOptions<NPTokenProviderOptions> options, ILogger<NPTokenProvider<TUser>> logger)
        : base(dataProtectionProvider, options, logger)
    { }
}
Enter fullscreen mode Exit fullscreen mode

We are here subclassing the DataProtectorTokenProvider. Nothing out of the ordinary except in the constructor we are passing NPTokenProviderOptions. The options need to be subclass of DataProtectionTokenProviderOptions.

NPTokenProviderOptions

Create a new file NPTokenProviderOptions.cs and paste in the below code.

using System;
using Microsoft.AspNetCore.Identity;

public class NPTokenProviderOptions : DataProtectionTokenProviderOptions
{
    public NPTokenProviderOptions()
    {
        Name = "NPTokenProvider";
        TokenLifespan = TimeSpan.FromMinutes(10);
    }
}
Enter fullscreen mode Exit fullscreen mode

We are setting options for the tokens to be created. You can change the Name and TokenLifeSpan to your liking.

DbContext

Almost every project needs database to store it's users and other data related to the project. Dotnet EF Framework provides a nice helper DbContext to handle sessions with the database and query and save entites. So create a subclass of IdentityDbContext which is in turn a subclass of DbContext. Name the file NPDataContext.cs.

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

public class NPDataContext : IdentityDbContext
{
    public NPDataContext(DbContextOptions<NPDataContext> options)
        : base(options)
    { }
}
Enter fullscreen mode Exit fullscreen mode

Startup.cs

We have created the classes; now time to configure them in our Startup.cs files. In ConfigureServices add the below code at the start.

var builder = services
.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<NPDataContext>();

var UserType = builder.UserType;
var provider = typeof(NPTokenProvider<>).MakeGenericType(UserType);
builder.AddTokenProvider("NPTokenProvider", provider);

services.AddDbContext<NPDataContext>(options =>
    options.UseInMemoryDatabase(Guid.NewGuid().ToString()));

services.AddAuthentication(options =>
{
    options.DefaultScheme = IdentityConstants.ExternalScheme;
});
Enter fullscreen mode Exit fullscreen mode

Also add app.UseAuthentication(); above app.UseAuthorization(); in Configure method.

NoPasswordController.cs

Let's create a controller for our login and verify API's. Create a NoPasswordController.cs file in your Controllers folder. Add the below content to the file.

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

namespace NoPasswordProject.Controllers
{
    [ApiController]
    [Route("[controller]/[action]")]
    public class NoPasswordController : ControllerBase
    {
        private readonly UserManager<IdentityUser> _userManager;

        public NoPasswordController(UserManager<IdentityUser> userManager)
        {
            _userManager = userManager;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We are injecting an instance of UserManager in our controller. UserManager is used for CRUD operations for a user as well as generating tokens and validating it.

Login API

Let's add a Login api which accepts an Email as input. The Email is the unique identifier for a user i.e. there should be a one-to-one relation ship between user and email.

Create a new function in your controller as below.

[HttpGet]
public async Task<ActionResult<String>> Login([FromQuery] string Email)
{
    // Create or Fetch your user from the database
    var User = await _userManager.FindByNameAsync(Email);
    if (User == null)
    {
        User = new IdentityUser();
        User.Email = Email;
        User.UserName = Email;
        var IdentityResult = await _userManager.CreateAsync(User);
        if (IdentityResult.Succeeded == false)
        {
            return BadRequest();
        }
    }

    var Token = await _userManager.GenerateUserTokenAsync(User, "NPTokenProvider", "nopassword-for-the-win");

    // DON'T RETURN THE TOKEN.
    // SEND IT TO THE USER VIA EMAIL.
    return NoContent();
}
Enter fullscreen mode Exit fullscreen mode

Here we are fetching a User from the database. If the user doesn't exist then we create a user. Make sure to set the UserName as well or it will give runtime error.

Then based on the user, we generate a UserToken. The GenerateUserTokenAsync takes the user, token provider and the purpose for generating a token.

The token provider string should be the one you have used in NPTokenProviderOptions. The purpose can be anything you want.

Send out the token to the user via a link in a nicely designed email. When the user will click on the link in the email it will open your front-end page. Consequently this page will request the Verify api.

Verify API

Let's add another api Verify that takes the Email and Token as query parameters.

[HttpGet]
public async Task<ActionResult<String>> Verify([FromQuery] string Token, [FromQuery] string Email)
{
    // Fetch your user from the database
    var User = await _userManager.FindByNameAsync(Email);
    if (User == null)
    {
        return NotFound();
    }

    var IsValid = await _userManager.VerifyUserTokenAsync(User, "NPTokenProvider", "nopassword-for-the-win", Token);
    if (IsValid)
    {
        // TODO: Generate a bearer token
        var BearerToken = "";
        return BearerToken;
    }
    return Unauthorized();
}
Enter fullscreen mode Exit fullscreen mode

We are again fetching the user based on email. As a result if we are not able to find the user return 404 Not Found.

We then continue to verify the user. VerifyUserTokenAsync takes user, token provider, purpose and token as input parameters. The purpose should be same as the one used while generating token.

If the token is not valid, return 401 Unauthorised. Otherwise return the bearer token. This is a good article on how to generate bearer token for the user.

In conclusion our article explains on how to allow users to login without a password. You can find the whole project here

Conclusion

Providing features was supposed to be the most important thing in the '90s. But today besides having great features, the convenience for the users is a priority.

We looked at one of the ways of providing convenience. That said, let us know in the comments below for more ways to do it.

Check here for more tutorials like this.

💖 💪 🙅 🚩
arjavdave
Arjav Dave

Posted on April 6, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related