Magic Links
Platform 6 can request P6 Auth generate signed identity-only tokens that can be used as the access_token
parameter of a magic link.
A magic link
is a URL that is embedded in an email that once clicked will initiate an action in P6 via an Http request.
The identity (and a number of other optional claims
) is established by validating and decoding the identity token.
The structure of identity-only tokens is similar to all other tokens used in P6, so can travel though P6 infrastructure without issue. They can be used to establish the identity
of a user, however, they hold no level or permission(s) within the P6 ecosystem.
Identity tokens are stateless
meaning they have no part persisted in P6 storage. This means they are extremely efficient and can easily scale. However, an identity token has no knowledge of its use, so it is the responsibility of an application using them to validate action state using workflow or transactional status for example.
Identity tokens have an expiry which means they do not present a long term security risk. When generating identity tokens a TTL (time to live) value must be given. This value should be as short as possible while allowing sufficient time for the users to use the magic links.
Identity tokens do not have to relate to a P6 Portal user. In fact the identity doesn’t even have to be an email address. It is just a unique identifier that your application can use to reference a transaction owner within P6.
Note
Additional validation allows the generation of identity tokens ONLY if the given identity(s) relate to registered users in P6 Portal (this is optional)
Magic links are best explained using a deployment scenario:
- Below we will generate identity tokens for two users. Each user has two additional
claims
encoded in their identity token: customernumber and accountref - The choice and use of
claims
is purely here as an example and to show that claims are available when a user identifies themselves using a magic link. - The identity tokens will then be added as part of a URL that will call the instance (via p6proxy)
- The URL is embedded in a HTML email and sent to each of the users
- We will deploy a REST route to listen for incoming HTTP GET requests on the path specified in the email template
- The script that is called when the REST route is initiated will extract the identity and claims from the access token (via pipeline variables)
- The script will create an HTML response that is returned to the user of the magic link as confirmation of an action that has been performed.
- Obviously the action to be performed is missing from this tutorial and will depend upon the intention of the overall application.
Step 1 Generate Tokens and Send Emails¶
This example uses two custom claims: customernumber
and accountref
. There can be upto 16 custom claims per identity token and each claim can be upto 512 characters in length
Note
It is advised that claims are use sparingly as they significantly increase the size of the identity tokens used.
def ids = [
new Tuple2('simon.temple@sidetrade.com', [customernumber:'093459273472345-987',accountref:'AJH9876']),
new Tuple2('stemple@sidetrade.com', [customernumber:'093459273472345-985',accountref:'AJB9846'])
]
p6.users.generateIdentityTokens(false, 1, ids) { identity, token ->
def html = p6.fm.process(p6.resource.get("email-template"), [name:'Simon', token:token])
p6.email.sendHtmlEmail('simon.temple@amalto.com', identity, html, [subject:'Message From Platform6'])
}
Execution of this script caused two emails to be sent. They look like this when displayed by the email client of the recipient:
Step 2 Deploy REST Route For Magic Link Request¶
p6.camel.getCtx().addRoutes(new RouteBuilder() {
void configure() {
rest().get("/apis/apps/custom")
.to("p6cmb://scripts?platform6.request.action=execute&id=RestMagicLink")
.id("RestMagicLink")
}
})
Note
The root context of /apis/ is important as this is the autheticated root context
and will validate and process access tokens
Step 3 Create Script (RestMagicLink) to Execute When Email Link Is Selected and called via the route¶
def email = p6.pipeline.get('platform6.request.user')
// Note: Pipeline variable `access_token` is available if required at this point
p6.log.info('Magic link processing for: ' + email + ' permissions: ' + p6.pipeline.get('platform6.request.user.permissions'))
def validPermissions = p6.permissions.hasPermissionsUsingPipelineRequest("user", "identity")
if (!validPermissions) {
p6.pipeline.put("CamelHttpResponseCode", "401")
p6.pipeline.put("body", "The supplied identity-only user does not have permission to use this instance!")
return
}
def accountRef = p6.pipeline.get('platform6.request.user.claim.accountref')
def customerNumber = p6.pipeline.get('platform6.request.user.claim.customernumber')
def html = p6.fm.process(p6.resource.get("page-template"), [email:email, accountref:accountRef, customernumber:customerNumber])
p6.pipeline.put('CamelHttpResponseCode', '200')
p6.pipeline.put('p6rest.Content-Type', 'text/html; charset=utf-8')
p6.pipeline.put('body', html)
p6.pipeline.put('p6rest.Content-Security-Policy',"default-src * 'unsafe-inline' 'unsafe-eval' data: blob:; ")
Warning
It is the responsibility of the script developer to validate the permissions
using the permissions DSL:
p6.permissions.hasPermission/hasPermissionsUsingPipelineRequest()
This must check the identity-only user has the fixed user=identity
permission that all identity-only users have and more importantly it checks the permission
applies to the Current Instance
! This will guard against mistakes such as Test
magic links being sent to Prod
users or vice versa.
It ensures the current instance is the one that generated this identity token
Note
In order to allow the browser to render my single HTML page response I had to lower the Content-Security-Policy
which is by default too high to allow embedded image and css rendering
Once the link is selected in the users email, the above script executes and the user will see the following in their browser:
Multiple magic links per email¶
This example shows a single HTML button used to trigger a magic link. The same identity token can be used as the access_token
in several magic links in the same email
A unique query parameter could be used to signify an action code
for example
Passwordless Authentication actions are limited but very scalable¶
The interactions between user and application using magic links is extremely limited when compared to the rich functionality offered by a Portal for example.
However, passwordless authentication offers a lightweight (stateless) and much more scalable user interaction flow as users do not have to exist as Portal users (no prior registration required)
User experience when tokens expire¶
When an identity token TTL elapses, the token expires and the user will receive an error message generated in their browser by the P6 Proxy as their connection attempt is rejected.
The magic link is lapsed at this point and re-generation is required.
Note
More detailed design documentation can be found here: https://amalto-jira.atlassian.net/wiki/spaces/RD/pages/1583185925/Platform+6+Magic+Links