MagicJS
mern.aiUniversityGitHub
  • Introduction to MagicJS
  • Why MagicJS?
  • Getting Started & Installation
  • Basic Guide
    • Create a new page using React
    • Navigate between pages
    • Create an API and integrate it with the frontend
    • Authenticate Users
      • Authorise based on Roles
    • Advanced State Management with useContent()
    • Perform CRUD Operations
    • Adding Realtime capabilities using socket
    • Handling file uploads and downloads
  • Advanced Guide
    • Understanding the concept of features in MagicJS
    • Using UI components & functions across multiple Magic Features
    • Advanced Routing of pages
    • Enable SSR
    • Access MongoDB
    • Styling pages using Tailwind CSS
  • Deploying
  • Update MagicJS
  • API References
    • Frontend
      • <LinkDisplay>
      • createSrc()
      • createUploader()
      • importUI()
      • loadConfig()
      • protected()
      • useParams()
      • useAxios()
      • useLogin()
      • useSocket()
      • useContent()
      • usePromise()
      • useNotification()
    • Backend
      • createBackendFunction()
      • data()
      • io()
      • ServerInstance()
      • utils
        • hash()
        • verifyHash()
        • initiateEmailVerification()
        • saveFileToUserUploads()
        • readFileFromUserUploads()
        • removeFileFromUserUploads()
        • assignRoleToUser()
        • unassignRoleFromUser()
        • findAllRolesByUser()
        • isUserInAnyRoles()
        • assignRoleToUser()
Powered by GitBook
On this page
  • Implementing Role-Based Features
  • Backend Logic
  • 1. Creating the "Tell Me a Joke" API
  • 2. Role Assignment and Removal
  • Ensuring Frontend Protection

Was this helpful?

  1. Basic Guide
  2. Authenticate Users

Authorise based on Roles

In this tutorial, we will explore how to implement user role authentication within your MagicJS application.

PreviousAuthenticate UsersNextAdvanced State Management with useContent()

Last updated 1 year ago

Was this helpful?

Before moving forward, it's essential to confirm that user authentication has been properly configured in your MagicJS application, as outlined in the previous section. We'll be utilizing the same files in this tutorial, so ensure that authentication is in place.

If you're unfamiliar with user authentication, please refer covering the necessary details regarding user authentication setup.

Implementing Role-Based Features

Implement a role-based feature where only users with a specific role can access certain functionalities.

For example, we'll create a "Tell Me a Joke" button that displays a joke, but only users with the "joker" role are allowed to view it.

Backend Logic

1. Creating the "Tell Me a Joke" API

  1. Create a backend file inside the main-features folder.

  2. Name the file as 'get-joke'.

  3. Add a joke to return.

  4. Implement role-based access control in the backend logic. Users without the required role will receive a message indicating they are not authorized to view the joke.

Refer the below snippet:

get-joke.server.tsx
import { createBackendFunction, useFunctionContext } from "@magicjs.dev/backend"

export default createBackendFunction(async function () {
    const ctx = useFunctionContext(this)

    if (ctx.isAuthenticated === false) {
        return "You have to sign in first"
    }

    if (await ctx.isCurrentUserInAnyRoles(["joker"]) === true) {
        return "What do you call a magic dog? A Labracadabrador."
    } else {
        return "You are not a joker."
    }
})
  • In the above code, ctx.isAuthenticated checks if the user is authenticated. If not, it returns a message indicating that the user needs to sign in.

  • Then, ctx.isCurrentUserInAnyRoles(["joker"]) checks if the current user has the role "joker". If true, it returns a joke message. If not, it returns a message indicating that the user is not a joker.

Invoke the API in a button.

  1. Add a button in the 'home.tsx' file and label it as Tell me a joke.

  2. Call the 'getJokeServer' in the button as shown in the below code.

home.tsx
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"

export default function Component(props: any) {
    const { current, logout } = useLogin()
    return (
        <div>
            <h1>{`Current User: ${current?.currentUser?.name}`}</h1>

            <Button onClick={() => loginServer('John')}>
                Login
            </Button>

            <Button onClick={() => getMyNameServer().then((name) => alert(name))}>
                Call Protected Backend
            </Button>

            {
                current.isAuthenticated === true ? (
                    <Button onClick={() => logout()}>
                        Logout
                    </Button>
                ) : null
            }
            
            <div>
                <Button onClick={() => getJokeServer().then((name) => alert(name))}>
                    Tell me a Joke
                </Button>
            </div>

        </div>
    )
}
Expand for Tailwind styled code.
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"

export default function Component(props: any) {
  const { current, logout } = useLogin()
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="font-semi-bold text-4xl tracking-wide pb-8">
        {`Current User: ${current?.currentUser?.name}`}
      </h1>

      <div>
        <Button onClick={() => loginServer("John")} className="text-xl w-40 h-14">
          Login
        </Button>

        <Button onClick={() => getMyNameServer().then((name) => alert(name))} className="text-xl w-120 h-14">
          Call Protected Backend
        </Button>

        {
          current.isAuthenticated === true ? (
            <Button onClick={() => logout()} className="text-xl w-40 h-14">
              Logout
            </Button>
          ) : null
        }

        <div className=" flex p-2.5 justify-center">
          <Button onClick={() => getJokeServer().then((name) => alert(name))} className="text-xl w-40 h-14">
            Tell me a Joke
          </Button>
        </div>
      </div>
    </div>
  )
}
  • Tell me a Joke button calls the getJokeServer function imported from get-joke.server and alerts the returned joke when clicked.

2. Role Assignment and Removal

To enable users to add or remove their roles, we'll create two backend APIs for role assignment and removal. Users can click the "Add Role" button to assign the "joker" role to themselves and subsequently remove it using the "Remove Role" button.

  1. Create 2 backed files and name them 'add-role' and remove-role' respectively.

Refer the below codes:

add-role.server.tsx
import { createBackendFunction, useFunctionContext, utils } from "@magicjs.dev/backend"

export default createBackendFunction(async function () {
    const ctx = useFunctionContext(this)

    await utils.assignRoleToUser(ctx.currentUser._id, 'joker');

    return "Message from server"
})
remove-role.server.tsx
import { createBackendFunction, useFunctionContext, utils } from "@magicjs.dev/backend"

export default createBackendFunction(async function () {
  const ctx = useFunctionContext(this)

  await utils.unassignRoleFromUser(ctx.currentUser._id, 'joker');

  return "Message from server"
})
  1. Invoke the add-role and remove-role with respective buttons in home.tsx.

Refer the code below.

home.tsx
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"
import addRoleServer from "./add-role.server"
import removeRoleServer from "./remove-role.server"

export default function Component(props: any) {
    const { current, logout } = useLogin()
    return (
        <div>
            <h1>{`Current User: ${current?.currentUser?.name}`}</h1>

            <Button onClick={() => loginServer('John')}>
                Login
            </Button>

            <Button onClick={() => getMyNameServer().then((name) => alert(name))}>
                Call Protected Backend
            </Button>

            {
                current.isAuthenticated === true ? (
                    <Button onClick={() => logout()}>
                        Logout
                    </Button>
                ) : null
            }
            
            <div>
                <Button onClick={() => addRoleServer().then(() => alert('Role added'))}>
                    Add Role
                </Button>
                <Button onClick={() => getJokeServer().then((name) => alert(name))}>
                    Tell me a Joke
                </Button>
                <Button onClick={() => removeRoleServer().then(() => alert('Role removed'))}>
                    Remove Role
                </Button>
            </div>
        </div>
    )
}
Expand for Tailwind styled code.
home.tsx
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"
import addRoleServer from "./add-role.server"
import removeRoleServer from "./remove-role.server"

export default function Component(props: any) {
  const { current, logout, isCurrentUserInAnyRoles } = useLogin()
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="font-semi-bold text-4xl tracking-wide pb-8">
        {`Current User: ${current?.currentUser?.name}`}
      </h1>

      <div>
        <Button onClick={() => loginServer("John")} className="text-xl w-40 h-14">
          Login
        </Button>

        <Button onClick={() => getMyNameServer().then((name) => alert(name))} className="text-xl w-120 h-14">
          Call Protected Backend
        </Button>

        {
          current.isAuthenticated === true ? (
            <Button onClick={() => logout()} className="text-xl w-40 h-14">
              Logout
            </Button>
          ) : null
        }

        <div className=" flex p-2.5 justify-center">
          <Button onClick={() => addRoleServer().then(() => alert('Role added'))} className="text-xl w-40 h-14">
            Add Role
          </Button>

          <Button onClick={() => getJokeServer().then((name) => alert(name))} className="text-xl w-40 h-14">
            Tell me a Joke
          </Button>

          <Button onClick={() => removeRoleServer().then(() => alert('Role removed'))} className="text-xl w-40 h-14">
            Remove Role
          </Button>
        </div>
      </div>
    </div>
  )
}
  • Add Role button calls the addRoleServer function imported from add-role.server and assigns the role 'joker' to the current user when clicked.

  • Remove Role button calls the removeRoleServer function imported from remove-role.server and unassigns the role 'joker' from the current user when clicked.

Ensuring Frontend Protection

Adding Frontend Protection ensures that frontend components and features are only accessible to users with the requisite roles.

In conjunction with backend role-based access control mechanisms, frontend role checks will be integrated using the isCurrentUserInAnyRoles function provided by MagicJS.

  1. Add function isCurrentUserInAnyRoles in the useLogin hook.

  2. Wrap the Tell me a joke button inside this function as shown in the below code.

Code:

home.tsx
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"
import addRoleServer from "./add-role.server"
import removeRoleServer from "./remove-role.server"

export default function Component(props: any) {
    const { current, logout, isCurrentUserInAnyRoles } = useLogin()
    return (
        <div>

            <h1>{`Current User: ${current?.currentUser?.name}`}</h1>

            <Button onClick={() => loginServer('John')}>
                Login
            </Button>

            <Button onClick={() => getMyNameServer().then((name) => alert(name))}>
                Call Protected Backend
            </Button>

            {
                current.isAuthenticated === true ? (
                    <Button onClick={() => logout()}>
                        Logout
                    </Button>
                ) : null
            }
            <div>
                <Button onClick={() => addRoleServer().then(() => alert('Role added'))}>
                    Add Role
                </Button>

                {
                    isCurrentUserInAnyRoles(['joker']) === true ? (
                        <Button onClick={() => getJokeServer().then((name) => alert(name))}>
                            Tell me a Joke
                        </Button>
                    ) : (
                        null
                    )
                }

                <Button onClick={() => removeRoleServer().then(() => alert('Role removed'))}>
                    Remove Role
                </Button>
            </div>
        </div>
    )
}
Expand for Tailwind styled code.
home.tsx
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"
import addRoleServer from "./add-role.server"
import removeRoleServer from "./remove-role.server"

export default function Component(props: any) {
  const { current, logout, isCurrentUserInAnyRoles } = useLogin()
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="font-semi-bold text-4xl tracking-wide pb-8">
        {`Current User: ${current?.currentUser?.name}`}
      </h1>

      <div>
        <Button onClick={() => loginServer("John")} className="text-xl w-40 h-14">
          Login
        </Button>

        <Button onClick={() => getMyNameServer().then((name) => alert(name))} className="text-xl w-120 h-14">
          Call Protected Backend
        </Button>

        {
          current.isAuthenticated === true ? (
            <Button onClick={() => logout()} className="text-xl w-40 h-14">
              Logout
            </Button>
          ) : null
        }

        <div className=" flex p-2.5 justify-center">
          <Button onClick={() => addRoleServer().then(() => alert('Role added'))} className="text-xl w-40 h-14">
            Add Role
          </Button>

          {
            isCurrentUserInAnyRoles(['joker']) === true ? (
              <Button onClick={() => getJokeServer().then((name) => alert(name))} className="text-xl w-40 h-14">
                Tell me a Joke
              </Button>
            ) : (
              null
            )
          }

          <Button onClick={() => removeRoleServer().then(() => alert('Role removed'))} className="text-xl w-40 h-14">
            Remove Role
          </Button>
        </div>
      </div>
    </div>
  )
}```typescript
import React from "react"
import { Button } from "antd"
import { useLogin } from "@magicjs.dev/frontend"
import loginServer from "./login.server"
import getMyNameServer from "./get-my-name.server"
import getJokeServer from "./get-joke.server"
import addRoleServer from "./add-role.server"
import removeRoleServer from "./remove-role.server"

export default function Component(props: any) {
  const { current, logout, isCurrentUserInAnyRoles } = useLogin()
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="font-semi-bold text-4xl tracking-wide pb-8">
        {`Current User: ${current?.currentUser?.name}`}
      </h1>

      <div>
        <Button onClick={() => loginServer("John")} className="text-xl w-40 h-14">
          Login
        </Button>

        <Button onClick={() => getMyNameServer().then((name) => alert(name))} className="text-xl w-120 h-14">
          Call Protected Backend
        </Button>

        {
          current.isAuthenticated === true ? (
            <Button onClick={() => logout()} className="text-xl w-40 h-14">
              Logout
            </Button>
          ) : null
        }

        <div className=" flex p-2.5 justify-center">
          <Button onClick={() => addRoleServer().then(() => alert('Role added'))} className="text-xl w-40 h-14">
            Add Role
          </Button>

          {
            isCurrentUserInAnyRoles(['joker']) === true ? (
              <Button onClick={() => getJokeServer().then((name) => alert(name))} className="text-xl w-40 h-14">
                Tell me a Joke
              </Button>
            ) : (
              null
            )
          }

          <Button onClick={() => removeRoleServer().then(() => alert('Role removed'))} className="text-xl w-40 h-14">
            Remove Role
          </Button>
        </div>
      </div>
    </div>
  )
}
```

Note: Please be advised that in order for the modifications to be implemented, it is necessary to refresh the page each time.

Implementing a state in the frontend file will continuously compute, eliminating the need for repetitive manual refreshing.

By leveraging user role authentication and role management in your MagicJS application, you can tailor access permissions and enhance security. Empowered with role-based access control, you can customize the user experience and ensure that users interact with features aligned with their roles.

Congratulations! You've learned how to authenticate a user role seamlessly in using the MagicJS framework.

🎉
MERN.AI
Authenticate Users
Authorise based on Roles
Output
Output
Output