-
Notifications
You must be signed in to change notification settings - Fork 6
02. In Memory Setup
This page will show how you can setup the middleware to use an in-memory user store. This won't persist user information over application and server reboots and won't scale past one server so you wouldn't want to use this in a production environment. It's easy to configure and a nice way to get up and running with minimal effort.
Ok, to start using the middleware in the startup.cs file add the following code.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/SignIn";
options.LogoutPath = "/SignOut";
options.ExpireTimeSpan = TimeSpan.FromHours(3);
})
.AddSqrl(options =>
{
options.CheckMilliSeconds = 1000;
options.CreateUser = SqrlCreateUser;
options.UserExists = UserExists;
options.UpdateUserId = UpdateUserId;
options.RemoveUser = RemoveUser;
options.LockUser = LockUser;
options.UnlockUser = UnlockUser;
options.GetUserVuk = GetUserVuk;
options.GetUserSuk = GetUserSuk;
options.Events.OnTicketReceived += OnTicketReceived;
})
You might have services.AuthenticationScheme
already and maybe the cookies part. The main bit is the AddSqrl method this tells Asp.net to wire up the middleware and set its options.
The code block above shows how to configure the options. All but the CheckMilliseconds, CreateUser and Events.OnTicketReceived options are required.
-
CheckMilliseconds: Specifies the number of milliseconds a login page will wait to poll to see if a login was a success. The default is 1000 (1 second).
-
CreateUser: This is an Action to create a SQRL user. The information it is passed is UserId, Suk, Vuk these values are explained in the terminology page. If you implement this, you should store the user to look up later. If you don't implement this the middleware assumes you only want to let existing users login.
-
UserExists: This is a Function that is passed the UserId and it is expected to return True if the UserId known to the system and False if the UserId is not known to the system.
-
UpdateUserId: SQRL allows a user to change their public id for their identity this method is responsible for updating the user store so that the user id is updated and the new Suk and Vuk values are stored along with this.
-
RemoveUser: This Action is intended to remove the ability to login as the provided UserId from a system User it is not a request to remove the user from the system and it is perfectly valid to leave the user logged in after this is done although you may want to make sure they have another way to login (e.g. Forms login/OAuth/SAML) so they are not permanently locked out there account.
-
LockUser/UnlockUser: These Actions allow the middleware to change the users account so it is locked/Unlocked to a SQRL login they are passed the UserId which should be changed.
-
GetUserVuk: This is a Function that is passed the UserId and it is expected to return the Vuk that was stored when the user created an account.
-
GetUserSuk: This Function much like the GetUserVuk Action is passed the UserId and it is expected to return the Suk that was stored when the user created an account.
-
Event.OnTicketReceived: If you have used any form of OAuth authentication with ASP.net Core you might have used this event. This event is triggered once the user is Authenticated and can be useful for setting roles which is what this example is doing.
You will notice that the options pointed to methods we will now look at each method in detail to help with your understanding on how SQRL will be handled.
As mentioned above the InMemory example uses static data to store users, this is helpful when starting with SQRL in a development/testing setting as there no need to wipe/recreate a database every time you do a test especially when it comes to locking/unlocking users.
In the example data is stored in one of two lists:
_sqrlUsers: this is a list of SqrlUser objects (SqrlUser is a class belonging to the example not included in the middleware). _sqrlAdminUserIds: this is a list of string which for the example are the UserIds you can place a known UserId into this list by hardcoding it in or doing some coding to allow this user to get the "SqrlAdminRole" role.
This method has SQRL prefixed to it as a code style it's not needed but will help differentiate it from the any normal user create method you may have.
The example methods code looks like this
private void SqrlCreateUser(string userId, string suk, string vuk, HttpContext context)
{
_sqrlUsers.Add(new SqrlUser()
{
UserId = userId,
Suk = suk,
Vuk = vuk
});
}
As you can see the SqrlCreateUser method is passed UserId, Suk, Vuk and a HttpContext. The context can be helpful when you want to get hold of a database or other services you have setup on your server in the case of this example it's not used.
The example simply puts the user into its list of users depending on your process/user flow you may want to do other checks like if there already logged in as another user so you can link the SQRL user to the logged in user.
This method will be called only when "UserExists" returned Unknown, so you don't have to check if the user already exists.
The example methods code looks like this
private UserLookUpResult UserExists(string userId, HttpContext context)
{
var user = _sqrlUsers.SingleOrDefault(x => x.UserId == userId);
return user == null ? UserLookUpResult.Unknown : user.Locked ? UserLookUpResult.Disabled : UserLookUpResult.Exists;
}
This method returns a "UserLookUpResult" which is an enum that the middleware provides for you to report back the state of a user.
There are three states a user can be in these are:
- Unknown: When you can't find the user by its user id
- Disabled: When the user requested their user to be locked for SQRL login
- Exists: When you find a user with the given UserId
The example methods code looks like this
private void UpdateUserId(string newUserId, string newSuk, string newVuk, string userId, HttpContext context)
{
var user = _sqrlUsers.Single(x => x.UserId == userId);
user.UserId = newUserId;
user.Suk = newSuk;
user.Vuk = newVuk;
}
This method is called when the user has altered their public UserId for this URL which results in the Suk and Vuk been change as well.
The example methods code looks like this
private void RemoveUser(string userId, HttpContext arg2)
{
_sqrlUsers.Remove(_sqrlUsers.Single(x => x.UserId == userId));
}
This method is called when the user wishes to remove the ability for their user to be logged into using SQRL. They don't have to be logged in to request this although you might wish to enforce this within this method. It is suggested that if they are logged in that you don't have to log them out as you may wish to get them to create a traditional login (Forms/OAuth/SAML) before they go so, they can access their accounts.
The example methods code looks like this
private void LockUser(string userId, HttpContext context)
{
_sqrlUsers.Single(x => x.UserId == userId).Locked = true;
}
This method is called when the user wishes to temporary lock the ability to user SQRL to login to their account.
The example methods code looks like this
private void UnlockUser(string userId, HttpContext context)
{
_sqrlUsers.Single(x => x.UserId == userId).Locked = false;
}
This method is called when a user’s account is locked and the user is wanting to reactivate the account they have had to carry out steps before this to validate they are who they say they are before this is called so you can assume it's safe to unlock the account at this point.
The example methods code looks like this
private string GetUserVuk(string userId, HttpContext context)
{
return _sqrlUsers.Single(x => x.UserId == userId).Vuk;
}
This method is called as part of the validation steps to allow a unlock or remove of a user’s account and it should return the last known Vuk for the user.
The example methods code looks like this
private string GetUserSuk(string userId, HttpContext context)
{
return _sqrlUsers.Single(x => x.UserId == userId).Suk;
}
This method should return the last known Suk for a user, the SQRL client can request this at anypoint so it's not easy to know when this may be asked for.
The example methods code looks like this
private Task OnTicketReceived(TicketReceivedContext context)
{
var userId = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, _sqrlAdminUserIds.Contains(userId) ? "SqrlAdminRole" : "SqrlUserRole")
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
return Task.CompletedTask;
}
This method is triggered as an event delegate and is your opportunity to carry out operations on the user’s identity and do application specific things.
Now you have all that setup you are ready to run. In the example when you run you are given a simple page that indicates if your logged in or not, the code for this looks like this:
@page
@model InMemory.Pages.IndexModel
<h1>Inmemory demo</h1>
@if (!User.Identity.IsAuthenticated)
{
<form method="post">
<button type="submit">Start SQRL login</button>
</form>
}
else
{
<p>You are now logged in as @User.Identity.Name</p>
<form method="get" action="/logout">
<button type="submit">Logout</button>
</form>
}
When a user is not logged in, they will see a "Start SQRL login" button when this is clicked it makes a POST request to the server the code for this looks like this:
public IActionResult OnPost()
{
return Challenge("SQRL");
}
This is a razor pages implementation the key part to note though is the call of Challenge to the "SQRL" scheme which is the default for the middleware.
This then redirects the user to the default callback path of "/login-sqrl" which then the middleware produces. The ability to create a custom SQRL page with your own style and branding is covered on the "Custom SQRL Login page" wiki page.
From this point on the middleware is handling requests and your job is done.
This page has covered the code in the InMemory example project and has shown a simple example of how to get up and running with SQRL login with little coding.
The example is used to test the middleware and should work out of the box with any version 1 SQRL client.