DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Unlocking the Benefits of a Private API in AWS API Gateway
  • Achieving High Genericity in Code
  • API and Security: From IT to Cyber
  • Securely Sign and Manage Documents Digitally With DocuSign and Ballerina

Trending

  • A Simple, Convenience Package for the Azure Cosmos DB Go SDK
  • Detection and Mitigation of Lateral Movement in Cloud Networks
  • Docker Base Images Demystified: A Practical Guide
  • FIPS 140-3: The Security Standard That Protects Our Federal Data
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. Create a Secure Registration API Using Low-Code and No-Code

Create a Secure Registration API Using Low-Code and No-Code

This article covers the creation of a secure registration API using low-code and no-code for your application.

By 
Thomas Hansen user avatar
Thomas Hansen
DZone Core CORE ·
Jan. 04, 24 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
2.9K Views

Join the DZone community and get the full member experience.

Join For Free

Allowing users to register in your app is one of those important things few know how to implement correctly. It requires knowledge about a whole range of complex things that few software developers have time to study.

In the video below, I am demonstrating how to use Magic's no-code and low-code features to create it from scratch in 15 minutes.


The point of the above process is it relies upon workflows and software development automation, utilizing no-code and low-code software development concepts, or "composables" if you wish.

This allows us to create software 1 million times faster than the average software developer because out of 1,000,000 things you need to think about, 999,995 of those things are already handled in Magic. Still, because of Magic's workflows and actions, you don't have to sacrifice either speed, security, or flexibility - Because each action is an atomic building block, allowing you to chain and orchestrate actions together any way you see fit.

This allows you to use these actions and orchestrate them together to meet your exact requirements. Never again do you have to answer "We don't have those features" - Simply because as delivery speed increases orders of magnitudes, the software development axiom fundamentally changes. But that's a topic for another article.

Let's go through what's required when creating a registration API to understand the complexity of the task, and what I did in those 15 minutes in the above YouTube video.

User Registration Concepts

When creating a user registration workflow there are a lot of things you'll have to consider to make your system secure and well-functioning to prevent your system from entering an invalid state. We need to consider bots, security, data quality, avoiding inserting partial or invalid data, etc. Below are the concepts we have to think about as we proceed.

Double Optin

User registration requires two endpoints; one endpoint allows the user to register, and another endpoint is sent to the user as a hyperlink he or she can click to verify his or her email address. The second endpoint verifies that a "secret" parameter sent to the user's email address was correctly supplied before we accept the user as a "real user", and this secret must be impossible to guess.

This logic makes it impossible for users to register without supplying our system with a valid email address. This is often referred to as "double Optin registration", and ensures you've got a real email address for every single valid user in your system.

reCAPTCHA

Another security measure such an endpoint would typically employ is reCAPTCHA to avoid having malicious actors invoke it thousands of times with random garbage to fill up your database with invalid users. reCAPTCHA ensures the invocation came from an actual human being, and not from a script created by some malicious actor. This further adds to the security of our system and ensures that a human being is registered and not some bot or script.

By combining reCAPTCHA with double optin you have two guarantees.

  • A human being registered.
  • The human being gave you a real email address he or she controls.

This increases the quality of the data you end up with in your system during registration.

Unique Emails and Usernames

In addition, we typically don't want to allow for the same email address to be used multiple times for creating multiple users. This implies that before we create our user, we'll need to check our existing users to see if the specified email address has been previously used for registering a user in our system.

In addition, we want to ensure that the username is available before we allow the user to register. This prevents the same username from being used multiple times, which obviously would be a bad thing. Typically this is automatic since most systems save usernames as unique columns in some database - However, if we implement our logic here, we can create more useful error messages, propagating to the client with intelligent information, allowing him or her to understand what's wrong when the system fails.

A message such as "UNIQUE constraint violation on XYZ" isn't particularly informative for the end user. It's much better to provide the user with "Username is already taken".

Storing Passwords Securely

Passwords should never be stored in plain text in a database. I'm not going to go through all the arguments here, but basically, you want to store passwords as hash, and you want to use slow hashing, with record-based salts. Luckily for us, this is an out-of-the-box feature in Magic we don't need to think about. For those interested in the details, Magic is using blowfish hashing with individual per-record-based salts, preventing an entire axiom of security vulnerabilities - But that's beside the point of this article.

All you need to know is that Magic is using the same password storage functionality as Linux, the CIA, and the NSA. It's rock solid!

If you're using something else than Magic, you need to think about this - However, we're using Magic, and this is an out-of-the-box feature we don't need to think about.

Validate Input

In addition to the above, we want to enforce on the server side that the user-provided values for each argument our system must have to function correctly. This prevents our workflows from partially executing, resulting in our system entering an invalid state for parts of its data. In magic, this is done by using a mandatory validator, that ensures our arguments are specified, and if not, throws an exception.

In addition, we want to make sure the user provided a real email address before we start our workflow, to prevent creating a partial user based upon erroneous data. This is done using an email validator. Notice, this doesn't prevent the user from providing an invalid email address, or some random Gmail he or she doesn't own. But combined with the above double Optin logic, this creates a high fault tolerant system, almost impossible to feed with invalid data.

Typically the above is done on the client side, but as we all know, client-side code might have bugs, allowing the system to be invoked with invalid data. For these reasons, we want to repeat this logic also on the server, regardless of whether or not we implemented it on the client side.

When you create software, you want your solutions to be as close to atomic as possible. The above ensures almost 100% perfect atomicity, where only if all arguments are valid, changes are persisted and state changes.

Allowing for state changes in your system because of "bad data", results in your system and its database becoming a "big junkyard" after a couple of years.

Our Endpoints

With the above explanation we can now type out what arguments our endpoints require. Our two endpoints require the following arguments.

register. post.hl

  • username
  • password
  • name
  • email
  • recaptcha_token

verify-email. get. hl

  • username
  • hashed username

Registration Flow

With the above information, we can now proceed to create our registration endpoint's logic. The following flow is how a registration typically works.

The user registers with a username, password, name, and email

  • Validate reCAPTCHA value
  • Ensure required arguments were given
  • Ensure email is an email address and not something else
  • Make sure the username is available
  • Create a user, making sure we store the password as BlowFish hashed with individual per-record-based salts
  • Associate user with two extra fields; name and email
  • Send an email with a double Optin link
    • Getting config value for magic:auth: secret
    • Concatenating username with auth secret
    • Hashing result of concatenated strings
    • Send an email to the user with a link to another HTTP GET endpoint that verifies the hash with username and hash value as QUERY parameters

User clicks links in email

  • Verifying hash
    • Getting config value for magic:auth: secret
    • Concatenating username with auth secret
    • Hashing result of concatenated strings
    • Verify the result of the above hash equals the QUERY parameter hash, and if not, throw an exception (conditional action)
  • Add user to a guest role
  • Redirect the user to some static welcome URL

The Code

The above is a rock-solid registration API logic, perfectly encapsulating almost everything you need to consider as you create a registration API. Still, I have not seen a single system in my entire life implemented using the above best practices.

The way we built it in the above YouTube video was almost "drag-drop". Still, the code encapsulates orders of magnitudes better code than every single system I have seen in my life as a professional software developer. Providing "registration services" is an entire axiom of SaaS business company ideas. I know at least a handful of companies providing the above as their single value proposition because of how easy it is to mess this up.

Below you can see the whole code required to accomplish the above.

register. post.hl

JavaScript
 
/*
 * Registers a user in your cool app!
 * 
 * Very cool!
 */
.arguments
   username:string
   password:string
   name:string
   email:string
   recaptcha_token:string

/*
 * Validates the specified reCAPTCHA token.
 *
 * This action will validate the specified reCAPTCHA token and throw an exception if validation fails,
 * unless the user is authenticated and belongs to the root role.
 */
execute:magic.workflows.actions.execute
   name:recaptcha
   filename:/misc/workflows/actions/security/recaptcha.hl
   arguments
      recaptcha_token:x:@.arguments/*/recaptcha_token
      threshold:decimal:0.3

/*
 * Ensures the spcified [args] was given.
 *
 * Will iterate through each [args] specified and verify it exists, and if not, the action will
 * throw an exception with a descriptive text explaining what argument was missing.
 */
execute:magic.workflows.actions.execute
   name:validators-mandatory
   filename:/misc/workflows/actions/security/validators-mandatory.hl
   arguments
      args
         username:x:@.arguments/*/username
         password:x:@.arguments/*/password
         name:x:@.arguments/*/name
         email:x:@.arguments/*/email

/*
 * Ensures the spcified [emails] are valid emails.
 *
 * Will iterate through each [emails] specified and verifying these, this action will
 * throw an exception with a descriptive text explaining what argument was not a valid email.
 */
execute:magic.workflows.actions.execute
   name:validators-email
   filename:/misc/workflows/actions/security/validators-email.hl
   arguments
      emails
         email:x:@.arguments/*/email

/*
 * Returns true if the specified username is available.
 *
 * Optionally apply [throw] as true, at which point the action will throw an exception if
 * the username is already taken.
 */
execute:magic.workflows.actions.execute
   name:username-available
   filename:/modules/registration/workflows/actions/username-available.hl
   arguments
      username:x:@.arguments/*/username
      throw:bool:true

/*
 * Registers the specified [username] as a user in the system, with the specified [password].
 *
 * Notice, [password] will be hashed using blowfish hashing.
 */
execute:magic.workflows.actions.execute
   name:users-create
   filename:/modules/registration/workflows/actions/users-create.hl
   arguments
      username:x:@.arguments/*/username
      password:x:@.arguments/*/password

/*
 * Associates the specified [username] with the specified [extra] fields.
 *
 * If extra field already exists for user, it will be changed - If not, it will be inserted.
 */
execute:magic.workflows.actions.execute
   name:extras-upsert
   filename:/modules/registration/workflows/actions/extras-upsert.hl
   arguments
      username:x:@.arguments/*/username
      extra
         name:x:@.arguments/*/name
         email:x:@.arguments/*/email

/*
 * Returns the specified [key] configuration setting.
 *
 * Notice, to traverse into for instance magic.foo.bar, you'll have to colon separate
 * your path as follows "magic:foo:bar".
 */
execute:magic.workflows.actions.execute
   name:config
   filename:/misc/workflows/actions/misc/config.hl
   arguments
      key:"magic:auth:secret"

/*
 * Joins the specified [values] into a single string.
 *
 * Notice, the [separator] is inserted between each string. If no [separator] argument is provided,
 * the strings will simply be concatenated instead.
 */
execute:magic.workflows.actions.execute
   name:strings-join
   filename:/misc/workflows/actions/strings/strings-join.hl
   arguments
      values
         .:x:@.arguments/*/username
         .:x:--/execute/=config/*/value
      separator:,

/*
 * Creates a hash of the specified [input].
 *
 * Use [algorithm] to override the default hashing algorithm used. The default hashing algorithm
 * is SHA 256.
 */
execute:magic.workflows.actions.execute
   name:hash
   filename:/misc/workflows/actions/misc/hash.hl
   arguments
      input:x:--/execute/=strings-join/*/result
      algorithm:sha256

// Becomes the body of our email
.body:@"Jo dude! Click the link below to confirm your email address.

https://thomastest-team.us.ainiro.io/magic/modules/my-cool-app/verify?username=[username]&token=[token]"

/*
 * Replaces the specified [source] string's [what] occurencies with [with].
 *
 * Returns the result as [result].
 */
execute:magic.workflows.actions.execute
   name:strings-replace-username
   filename:/misc/workflows/actions/strings/strings-replace.hl
   arguments
      source:x:@.body
      what:[username]
      with:x:@.arguments/*/username

/*
 * Replaces the specified [source] string's [what] occurencies with [with].
 *
 * Returns the result as [result].
 */
execute:magic.workflows.actions.execute
   name:strings-replace
   filename:/misc/workflows/actions/strings/strings-replace.hl
   arguments
      source:x:--/execute/=strings-replace-username/*/result
      what:[token]
      with:x:--/execute/=hash/*/result

/*
 * Sends an email to the specified [name]/[email] recipient, with the specified [subject] and [body].
 *
 * Optionally supply [from] and [from-email] as name/email sender. If you don't supply from, this action
 * will use the default sender settings from your configuration.
 */
execute:magic.workflows.actions.execute
   name:email
   filename:/misc/workflows/actions/misc/email.hl
   arguments
      html:bool:false
      name:x:@.arguments/*/name
      email:x:@.arguments/*/email
      subject:Please verify your email address
      body:x:--/execute/=strings-replace/*/result
return
   result:success


verify. get. hl

JavaScript
 
/*
 * Allows the user to verify his or her email address
 * 
 * Very cool app!
 */
.arguments
   token:string
   username:string

/*
 * Returns the specified [key] configuration setting.
 *
 * Notice, to traverse into for instance magic.foo.bar, you'll have to colon separate
 * your path as follows "magic:foo:bar".
 */
execute:magic.workflows.actions.execute
   name:config
   filename:/misc/workflows/actions/misc/config.hl
   arguments
      key:"magic:auth:secret"

/*
 * Joins the specified [values] into a single string.
 *
 * Notice, the [separator] is inserted between each string. If no [separator] argument is provided,
 * the strings will simply be concatenated instead.
 */
execute:magic.workflows.actions.execute
   name:strings-join
   filename:/misc/workflows/actions/strings/strings-join.hl
   arguments
      values
         .:x:@.arguments/*/username
         .:x:--/execute/=config/*/value
      separator:,

/*
 * Creates a hash of the specified [input].
 *
 * Use [algorithm] to override the default hashing algorithm used. The default hashing algorithm
 * is SHA 256.
 */
execute:magic.workflows.actions.execute
   name:hash
   filename:/misc/workflows/actions/misc/hash.hl
   arguments
      input:x:--/execute/=strings-join/*/result
      algorithm:sha256

/*
 * Executes the specified [action] if the specified [condition] is true.
 *
 * Will pass in all arguments specified to the action. The [condition] can be two different
 * values or expressions, and the [comparison] can be eq, neq, mt, mte, lt, lte, or some other
 * comparison operator.
 *
 * The action will use the [comparison] to compare the [lhs] and [rhs] values/expressions.
 */
execute:magic.workflows.actions.execute
   name:execute-action-if
   filename:/misc/workflows/actions/misc/execute-action-if.hl
   arguments
      lhs:x:--/execute/=hash/*/result
      comparison:neq
      rhs:x:@.arguments/*/token
      action:/misc/workflows/actions/misc/error.hl
      arguments
         message:Bogus token, go home!!
         status:400
         public:true

/*
 * Associates the specified [username] with the specified [roles] roles.
 *
 * Notice, does not remove roles from user, only adds roles. This action will also throw
 * if you try to associate a user with a role the user is already associated with.
 */
execute:magic.workflows.actions.execute
   name:roles-add
   filename:/modules/registration/workflows/actions/roles-add.hl
   arguments
      roles
         .:guest
      username:x:@.arguments/*/username

// Redirects the client to the specified [url].
execute:magic.workflows.actions.execute
   name:http-redirect
   filename:/misc/workflows/actions/http/http-redirect.hl
   arguments
      url:"https://ainiro.io"


Bonus Code

In addition to the above code, I have included an authenticate API endpoint below. I didn't create this in the YouTube video, but it took me 1 minute. This implies that I spent 16 minutes in total delivering a registration and authentication API backend module.

JavaScript
 
/*
 * Authenticates the user
 * 
 * Very cool app!!
 */
.arguments
   username:string
   password:string

/*
 * Authenticates the user with the specified [username] and [password].
 *
 * Returns a JWT Bearer token that can be used for consecutive requests authorizing user according
 * to his or her roles.
 */
execute:magic.workflows.actions.execute
   name:authenticate
   filename:/modules/registration/workflows/actions/authenticate.hl
   arguments
      username:x:@.arguments/*/username
      password:x:@.arguments/*/password

// Returns the result of your last action.
return-nodes:x:@execute/*


Conclusion

In total, the solution contains 303 lines of code. Divided by 16 minutes, that becomes 18 lines of code per minute. Almost one line of code per second. If I hadn't created the YouTube video at the same time I created the code, I could probably have tripled my speed, implying one line of code per second, building the entire solution in 5 minutes.

This becomes 604,800 lines of code per month, assuming I code for 40 hours per week

Realising the industry average is 325 lines of code per month, this implies with Magic you can in theory become 1,860 times more productive - Ignoring whether or not LOC is a good measurement of productivity.

Of course, measuring productivity by number of lines of code per minute is probably not a very good metric. However, the above is highly useful code, arguably production-ready as is, and I have seen senior software developers mess with the above for months without being able to do it correctly.

But don't believe me, go ask one of your team's senior devs how much time he or she needs to implement user registration, based upon RBAC, and all industry best practices, and try to give him the task. Then tell him you need an administration GUI to be able to administrate your users and roles.

Even a super human software developer would easily spend weeks, if not months delivering the above! Yet again, I did it live on YouTube, and I did it in 15 minutes!

In addition, most online tutorials you'll find about registration APIs will happily skip the most important parts, with a TODO hidden somewhere in a comment, saying stuff like "TODO: Implement security". The above have almost no TODOs. I could have provided a password validator maybe, to ensure the length of passwords was at least 12 characters or something - But that's the only TODO for the above code.

In the above YouTube video, I arguably created a "perfect" solution in 15 minutes. I think that's cool.

API security Password manager low code no code

Published at DZone with permission of Thomas Hansen. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Unlocking the Benefits of a Private API in AWS API Gateway
  • Achieving High Genericity in Code
  • API and Security: From IT to Cyber
  • Securely Sign and Manage Documents Digitally With DocuSign and Ballerina

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!