magic.lambda.auth
17.2.0
dotnet add package magic.lambda.auth --version 17.2.0
NuGet\Install-Package magic.lambda.auth -Version 17.2.0
<PackageReference Include="magic.lambda.auth" Version="17.2.0" />
paket add magic.lambda.auth --version 17.2.0
#r "nuget: magic.lambda.auth, 17.2.0"
// Install magic.lambda.auth as a Cake Addin #addin nuget:?package=magic.lambda.auth&version=17.2.0 // Install magic.lambda.auth as a Cake Tool #tool nuget:?package=magic.lambda.auth&version=17.2.0
magic.lambda.auth - How to do authentication and authorisation from Hyperlambda
The magic.lambda.auth project contains authentication and authorization helper slots for Magic. The project allows you to create and consume JWT tokens, to secure your Magic cloudlet. The project contains the following slots.
- [auth.ticket.get] - Returns the JWT token's payload as a lambda structure, and also verifies the token in the process
- [auth.ticket.create] - Creates a new JWT token, that you can return to your client, any ways you see fit
- [auth.ticket.refresh] - Refreshes a JWT token. Useful for refreshing a token before it expires, which would require the user to login again
- [auth.ticket.verify] - Verifies a JWT token, and that the user is logged in, in addition to (optionally) that he belongs to one roles supplied as a comma separated list of roles
- [auth.ticket.in-role] - The same as [auth.ticket.verify], but returns true or false, depending upon whether or not user is in any of the roles provided or not
Notice, you will have to modify your auth:secret
configuration setting, to provide a unique salt for your installation.
If you don't do this, some adversary can easily reproduce your tokens, and impersonate your users. Example of
an "appsettings.json" secret you could apply can be found below (don't use the exact same salt, the idea is to provide
a random salt, unique for your installation)
However, during installation, Magic will automatically create a random secret for you. Below is an example of how this might look like.
{
"auth": {
"secret":"some-rubbish-random23545456-characters-goes-hereqwertygfhdfSDFGFDdfgh45fDFGH"
}
}
The idea is that your SymmetricSecretKey
is based upon the above configuration setting, implying you should keep it
safe the same way you'd keep the pin code to your ATM card safe.
Below is an example of how to create a new JWT ticket.
auth.ticket.create
username:foo
roles
.:howdy
.:world
The above will return something resembling the following, minus the carriage returns, which are added for clarity.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImZvbyIsInJvbGUiOlsiaG93ZHkiLCJ3b3Js
ZCJdLCJuYmYiOjE2MTc5NDQyMzYsImV4cCI6MTYxNzk1MTQzNiwiaWF0IjoxNjE3OTQ0MjM2fQ.
9eYROea_r-TJGT5k4H0DaGDW6LziHhEsK4on-uc-ECc
Typically Magic takes care of authentication for you, by exposing an authenticate
endpoint for
you, where you can submit a username and a password, for then to have your JWT token returned accordingly.
You can also associate any claims you wish with your JWT token, by adding a key/value [claims]
argument to your invocation as you create your JWT token. Below is an example.
auth.ticket.create
username:foo
roles
.:howdy
.:world
claims
id:578
ssid:xyzqwerty67
All additional (non-roles) claims will have their values converted to string though, which is more of a restriction from .Net and the JWT standard then a restriction of Magic.
Notice - Also notice that if you create a JWT token with a user belonging to only one role, the roles claims will not be an array, but simply a string. Yet again, this is simply how .Net works, and there is little we can do about this. Hence, remember to check in your frontend as you retrieve your roles if the roles claims is a string or an array of strings before associating your user with a role in your client logic.
When you invoke [auth.ticket.create] or [auth.ticket.refresh] you can choose to either provide an [expires] which is a future date for when the ticket expires, or a [duration] which is a positive integer informing the slot of how many minutes the token should last before it's no longer valid.
JWT tokens internals
A JWT token is actually just 3 JSON objects, that have been base64 encoded, and afterwards each entity is separated by a period (.). In fact, you can inspect a JWT token at for instance JWT.io to inspect its internals. A JWT token has 3 parts.
- The header providing meta information
- The payload being the actual data of the token
- The signature which is basically just a HASH based upon your token's payload, salted with your auth secret
Unless an adversary knows your auth secret, it becomes impossible to create a valid JWT token. However, your token is still vulnerable for being stolen, so make sure you only return it over a secure HTTP connection.
Using your JWT token
Magic expects the token to be specified as a Bearer
token in your HTTP invocation's Authorization
HTTP header.
To use the token therefor implies adding an HTTP Authorization
header, and setting its value to something resembling
the following.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.xyz.qwerty
You can see how Magic does this internally by inspecting what the Magic dashboard transmits to the backend after having authenticated using e.g. Chrome Developer Tools.
Refreshing your JWT token
JWT tokens have an expiration date. This is typically configured in your "appsettings.json" file in your Magic
backend as magic:auth:valid-minutes
, and once this time has passed, the token will no longer be considered
valid, and using the token after this period, will result in an "Access Denied" being returned from the server.
However, since JWT token are just JSON, and the expiration time is just a Unix timestamp, you can parse the JWT token in your frontend, calculate the expiration date, and make sure you refresh your token before the expiration date is reached, and replace your existing token with the new token returned by Magic.
Magic contains a refresh
token endpoint to achieve this. Internally this endpoint will use
the [auth.ticket.refresh] slot to create a new token, where the only difference between your old and your
new token is that it has a new expiration date.
Securing your endpoints
To secure an endpoint, and for instance require the user to belong to a specific role in order to be allowed to invoke an endpoint, you might add something such as the following to the top of your Hyperlambda files.
auth.ticket.verify:root, admin, user
The above will throw an exception unless the endpoint is given a valid JWT token, and the user belongs to one of the roles that you pass in as a comma separated list of role in its invocation. In fact, you can try this out, by pasting the following into your Hyperlambda evaluator in your dashboard and execute it.
auth.ticket.verify:non-existing-role
Inspecting your JWT token's payload
The following slots allows you to inspect the token's payload, implying get the username and roles the current HTTP request originates from.
- [auth.ticket.get] - Returns the username and the roles the user belongs to
- [auth.ticket.in-role] - Convenience slot returning true if user belongs to the specified role(s)
Below is an example of how these are used.
auth.ticket.in-role:foo, root
auth.ticket.get
The first slot return true because your root user belongs to one of the roles you supplied as a comma separated list of roles. The second slot above returns the username, and all the roles the user belongs to as a structured lambda object. As a general rule of thumb though, you'd not want to secure your endpoints using the above slots, but rather the verify slot above, to avoid having errors in your code resulting in that an unauthorized user gains access to invoke an endpoint he should not be allowed to invoke. The [auth.ticket.get] slot will also return any custom claims you have associated with your JWT token as it was created.
How to use [auth.ticket.get]
This slot returns the currently authenticated user, its roles, and what claims the user has. Below is example usage. The slot returns the username as the value of its invocation and the roles as a [roles] list.
auth.ticket.get
Like all auth related slots, it only makes sense to invoke from within a context of having an authorised user, such as an HTTP endpoint intended to be invoked by an authenticated user.
How to use [auth.ticket.create]
This slot create a new JWT token for you, and accepts a username and optionally a list of roles. Below is some example usage.
auth.ticket.create
username:foo
roles
.:howdy
.:world
Like all auth related slots, it only makes sense to invoke from within a context of having an authorised user, such as an HTTP endpoint intended to be invoked by an authenticated user.
How to use [auth.ticket.refresh]
This slot returns a new JWT token from an old existing (and valid) token. However, the new token will have a new expiration date, further into the future, allowing you to create a timer that occassionally invokes the server to create a new token, preventing the user from being logged out due to having his or her JWT token becoming expired.
auth.ticket.refresh
Like all auth related slots, it only makes sense to invoke from within a context of having an authorised user, such as an HTTP endpoint intended to be invoked by an authenticated user.
How to use [auth.ticket.verify]
This slot verifies that the currently authenticated user belongs to one or more roles provided as a comma separated list of names. If the user does not belong to any of the specified roles, the slot will throw an exception. Below is example usage.
auth.ticket.verify:root,admin,guest
Like all auth related slots, it only makes sense to invoke from within a context of having an authorised user, such as an HTTP endpoint intended to be invoked by an authenticated user.
How to use [auth.ticket.in-role]
This slot verifies that the currently authenticated user belongs to one or more roles provided as a comma separated list of names. Contrary to the [auth.ticket.verify] slot this slot will not throw an exception if the user does not belong to one or more roles, but simply return false. Below is example usage.
auth.ticket.in-role:root,admin,guest
Like all auth related slots, it only makes sense to invoke from within a context of having an authorised user, such as an HTTP endpoint intended to be invoked by an authenticated user.
Magic's GitHub project page
Magic is 100% Open Source and you can find the primary project GitHub page here.
Project website for magic.lambda.caching
The source code for this repository can be found at github.com/polterguy/magic.lambda.auth, and you can provide feedback, provide bug reports, etc at the same place.
Copyright and maintenance
The projects is copyright Thomas Hansen 2023 - 2024, and professionally maintained by AINIRO.IO.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- magic.node.extensions (>= 17.2.0)
- magic.signals.contracts (>= 17.2.0)
- Microsoft.AspNetCore.Authentication.JwtBearer (>= 8.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on magic.lambda.auth:
Package | Downloads |
---|---|
magic.library
Helper project for Magic to wire up everything easily by simply adding one package, and invoking two simple methods. When using Magic, this is (probably) the only package you should actually add, since this package pulls in everything else you'll need automatically, and wires up everything sanely by default. To use package go to https://polterguy.github.io |
|
magic.lambda.ad-auth
Active Directory authentication helper project for Magic, giving you support for authenticating over AD from Hyperlambda. To use package go to https://polterguy.github.io |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
17.2.0 | 431 | 1/22/2024 |
17.1.7 | 188 | 1/12/2024 |
17.1.6 | 169 | 1/11/2024 |
17.1.5 | 185 | 1/5/2024 |
17.0.1 | 215 | 1/1/2024 |
17.0.0 | 368 | 12/14/2023 |
16.11.5 | 355 | 11/12/2023 |
16.9.0 | 340 | 10/9/2023 |
16.7.0 | 555 | 7/11/2023 |
16.4.1 | 416 | 7/2/2023 |
16.4.0 | 425 | 6/22/2023 |
16.3.1 | 379 | 6/7/2023 |
16.3.0 | 369 | 5/28/2023 |
16.1.9 | 669 | 4/30/2023 |
15.10.11 | 509 | 4/13/2023 |
15.9.1 | 641 | 3/27/2023 |
15.9.0 | 486 | 3/24/2023 |
15.8.2 | 529 | 3/20/2023 |
15.7.0 | 441 | 3/6/2023 |
15.5.0 | 1,687 | 1/28/2023 |
15.2.0 | 747 | 1/18/2023 |
15.1.0 | 1,254 | 12/28/2022 |
14.5.7 | 811 | 12/13/2022 |
14.5.5 | 879 | 12/6/2022 |
14.5.1 | 784 | 11/23/2022 |
14.5.0 | 705 | 11/18/2022 |
14.4.5 | 793 | 10/22/2022 |
14.4.1 | 892 | 10/22/2022 |
14.4.0 | 781 | 10/17/2022 |
14.3.1 | 1,421 | 9/12/2022 |
14.3.0 | 765 | 9/10/2022 |
14.1.3 | 1,037 | 8/7/2022 |
14.1.2 | 787 | 8/7/2022 |
14.1.1 | 810 | 8/7/2022 |
14.0.14 | 844 | 7/26/2022 |
14.0.12 | 809 | 7/24/2022 |
14.0.11 | 782 | 7/23/2022 |
14.0.10 | 799 | 7/23/2022 |
14.0.9 | 801 | 7/23/2022 |
14.0.8 | 866 | 7/17/2022 |
14.0.5 | 927 | 7/11/2022 |
14.0.4 | 904 | 7/6/2022 |
14.0.3 | 890 | 7/2/2022 |
14.0.2 | 786 | 7/2/2022 |
14.0.0 | 1,006 | 6/25/2022 |
13.4.0 | 2,265 | 5/31/2022 |
13.3.4 | 1,635 | 5/9/2022 |
13.3.0 | 1,107 | 5/1/2022 |
13.2.0 | 1,315 | 4/21/2022 |
13.1.0 | 1,218 | 4/7/2022 |
13.0.0 | 900 | 4/5/2022 |
11.0.5 | 1,576 | 3/2/2022 |
11.0.4 | 776 | 2/22/2022 |
11.0.3 | 782 | 2/9/2022 |
11.0.2 | 790 | 2/6/2022 |
11.0.1 | 786 | 2/5/2022 |
10.0.21 | 778 | 1/28/2022 |
10.0.20 | 775 | 1/27/2022 |
10.0.19 | 782 | 1/23/2022 |
10.0.18 | 740 | 1/17/2022 |
10.0.15 | 964 | 12/31/2021 |
10.0.14 | 577 | 12/28/2021 |
10.0.7 | 1,555 | 12/22/2021 |
10.0.5 | 791 | 12/18/2021 |
9.9.9 | 1,740 | 11/29/2021 |
9.9.3 | 975 | 11/9/2021 |
9.9.2 | 657 | 11/4/2021 |
9.9.0 | 782 | 10/30/2021 |
9.8.9 | 743 | 10/29/2021 |
9.8.7 | 696 | 10/27/2021 |
9.8.6 | 680 | 10/27/2021 |
9.8.5 | 739 | 10/26/2021 |
9.8.1 | 1,175 | 10/21/2021 |
9.8.0 | 692 | 10/20/2021 |
9.7.9 | 661 | 10/19/2021 |
9.7.7 | 1,049 | 10/17/2021 |
9.7.5 | 974 | 10/14/2021 |
9.7.0 | 902 | 10/9/2021 |
9.6.6 | 1,237 | 8/14/2021 |
9.2.0 | 6,411 | 5/26/2021 |
9.1.4 | 1,292 | 4/21/2021 |
9.1.0 | 1,110 | 4/14/2021 |
9.0.0 | 909 | 4/5/2021 |
8.9.9 | 1,017 | 3/30/2021 |
8.9.3 | 1,567 | 3/19/2021 |
8.9.2 | 1,030 | 1/29/2021 |
8.9.1 | 1,011 | 1/24/2021 |
8.9.0 | 1,130 | 1/22/2021 |
8.6.9 | 2,936 | 11/8/2020 |
8.6.6 | 1,967 | 11/2/2020 |
8.6.0 | 3,907 | 10/28/2020 |
8.5.0 | 1,890 | 10/23/2020 |
8.4.0 | 5,470 | 10/13/2020 |
8.3.1 | 2,636 | 10/5/2020 |
8.3.0 | 1,260 | 10/3/2020 |
8.2.2 | 1,976 | 9/26/2020 |
8.2.1 | 1,289 | 9/25/2020 |
8.2.0 | 1,345 | 9/25/2020 |
8.1.17 | 6,557 | 9/13/2020 |
8.1.16 | 591 | 9/13/2020 |
8.1.15 | 1,868 | 9/12/2020 |
8.1.11 | 2,469 | 9/11/2020 |
8.1.10 | 1,320 | 9/6/2020 |
8.1.9 | 1,308 | 9/3/2020 |
8.1.8 | 1,289 | 9/2/2020 |
8.1.7 | 1,198 | 8/28/2020 |
8.1.4 | 1,216 | 8/25/2020 |
8.1.3 | 1,295 | 8/18/2020 |
8.1.2 | 1,218 | 8/16/2020 |
8.1.1 | 1,281 | 8/15/2020 |
8.1.0 | 604 | 8/15/2020 |
8.0.1 | 2,639 | 8/7/2020 |
8.0.0 | 1,231 | 8/7/2020 |
7.0.1 | 1,358 | 6/28/2020 |
7.0.0 | 1,267 | 6/28/2020 |
5.0.0 | 7,335 | 2/25/2020 |
4.0.4 | 7,747 | 1/27/2020 |
4.0.3 | 1,262 | 1/27/2020 |
4.0.2 | 1,414 | 1/16/2020 |
4.0.1 | 1,386 | 1/11/2020 |
4.0.0 | 1,392 | 1/5/2020 |
3.1.0 | 6,194 | 11/10/2019 |
3.0.0 | 3,849 | 10/23/2019 |
2.0.2 | 6,012 | 10/18/2019 |
2.0.1 | 2,871 | 10/15/2019 |
2.0.0 | 1,630 | 10/13/2019 |
1.1.9 | 1,349 | 10/11/2019 |
1.1.8 | 1,327 | 10/10/2019 |
1.1.7 | 639 | 10/9/2019 |
1.1.6 | 637 | 10/7/2019 |
1.1.5 | 604 | 10/6/2019 |
1.1.4 | 605 | 10/6/2019 |
1.1.2 | 639 | 10/5/2019 |