Add user sign up
10 min
In this step, we implement user sign up using SCIM APIs and OAuth 2.0 Client Credentials Flow. The implementation includes user creation, role assignment, and error handling.
Key steps of the sign up flow:
Create API route¶
You can create a new API route at app/api/signup/route.ts
to handle user registration and create helper functions to manage user creation, role assignment, and API interactions with Asgardeo. This route handles the complete sign-up flow, from validating user input to creating new users or updating existing ones with appropriate roles. The implementation should be placed in the POST
method of this route file.
// Add imports (e.g., helper functions)
export async function POST(req: Request) {
// implementation
}
Implementation¶
Now that you have created your API route, follow the steps below to implement the sign-up functionality in the POST
method handler. After implementing the main flow, we'll create the helper functions needed for managing user creation, role assignment, and API interactions with Asgardeo:
-
Extract user input
First, we extract user details (email, password, firstName, and lastName) from the request body. If any required field is missing, we return a 400 Bad Request error.
app/api/signup/route.tsconst { email, password, firstName, lastName } = await req.json(); if (!email || !password || !firstName || !lastName) { return Response.json( { error: "Missing required fields" }, { status: 400 } ); }
-
Get an access token from the root organization
To interact with Asgardeo's SCIM API, we need an OAuth 2.0 access token. We obtain this using the Client Credentials grant type.
app/api/sign-up/route.tsconst tokenResponse = await fetch( process.env.AUTH_ASGARDEO_ISSUER, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: process.env.AUTH_ASGARDEO_ID!, client_secret: process.env.AUTH_ASGARDEO_SECRET!, scope: process.env.AUTH_SCOPE!, }).toString(), } );
If the token request fails, we return an error.
app/api/sign-up/route.tsif (!tokenResponse.ok) { throw new Error( `HTTP error! status: ${tokenResponse.status}` ); }
Once successful, we extract the access token:
app/api/sign-up/route.tsconst tokenData = await tokenResponse.json(); const accessToken = tokenData?.access_token;
-
Retrieve Application ID
To assign the user to a role, we first need the Application ID. We fetch it using the application name.
app/api/sign-up/route.tsconst appId = await getAppId(accessToken); if (!appId) { return Response.json( { error: "Failed to fetch application details" }, { status: 500 } ); }
-
Retrieve Role ID
Using the App ID, we fetch the Role ID associated with the application.
app/api/sign-up/route.tsconst roleId = await getRoleId(accessToken, appId); if (!roleId) { return Response.json( { error: "Failed to fetch role details" }, { status: 500 } ); }
-
Check Existing User
Before creating a new user, we check if the user already exists.
app/api/sign-up/route.tsconst existingUser = await getUser(accessToken, email); if (existingUser) { const isAdmin = existingUser?.roles?.includes( process.env.ADMIN_ROLE_NAME! ); if (isAdmin) { return Response.json( { error: "User already exists and has admin role" }, { status: 400 } ); } }
-
Create User or Assign Role
If the user doesn't exist, create them. Otherwise, assign the role to the existing user.
app/api/sign-up/route.tsif (!existingUser) { // Create new user const userId = await createUser( accessToken, email, firstName, lastName, password ); if (!userId) { return Response.json( { error: "User creation failed" }, { status: 500 } ); } // Assign role to new user if (await assignUserRole(accessToken, roleId, userId)) { return Response.json( { message: "User registered successfully!" }, { status: 200 } ); } } else { // Assign role to existing user if (await assignUserRole(accessToken, roleId, existingUser.id)) { return Response.json( { message: "User role assigned successfully!" }, { status: 200 } ); } }
Helper functions required for the above implementation:
You can create these functions inside app/api/services/
path.
// Get Application ID
async function getAppId(accessToken: string): Promise<string | null> {
const response = await fetch(
`${process.env.ASGARDEO_BASE_URL}/api/server/v1/applications?filter=name%20eq%20${process.env.APP_NAME}`,
{
method: "GET",
headers: { Authorization: `Bearer ${accessToken}` },
}
);
if (!response.ok) return null;
const data = await response.json();
return data?.applications[0]?.id || null;
}
// Get Role ID
async function getRoleId(accessToken: string, appId: string): Promise<string | null> {
const response = await fetch(
`${process.env.ASGARDEO_BASE_URL}/scim2/v2/Roles?filter=displayName%20eq%20${encodeURIComponent(process.env.ADMIN_ROLE_NAME!)}%20and%20audience.value%20eq%20${appId}`,
{
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
}
);
if (!response.ok) return null;
const data = await response.json();
return data?.Resources?.[0]?.id || null;
}
// Check if user exists
async function getUser(accessToken: string, email: string): Promise<any | null> {
const response = await fetch(
`${process.env.ASGARDEO_BASE_URL}/scim2/Users?filter=emails eq "${email}"`,
{
method: "GET",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
},
}
);
if (!response.ok) return null;
const data = await response.json();
return data?.totalResults > 0 ? data?.Resources[0] : null;
}
// Create user
export async function createUser(
accessToken: string,
email: string,
firstName: string,
lastName: string,
password: string
): Promise<string | null> {
const response = await fetch(`${process.env.ASGARDEO_BASE_URL}/scim2/Users`, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
emails: [{ primary: true, value: email }],
name: { familyName: lastName, givenName: firstName },
password: password,
userName: `DEFAULT/${email}`,
}),
});
if (!response.ok) return null;
const data = await response.json();
return data?.id || null;
}
// Assign role to user
export async function assignRole(accessToken: string, roleId: string, userId: string) {
const response = await fetch(
`${process.env.ASGARDEO_BASE_URL}/o/scim2/v2/Roles/${roleId}`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
Operations: [{ op: "add", path: "users", value: [{ value: userId }] }],
}),
}
);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
}
Finally, if all steps succeed, we return a success message and use that in the sign up form component.
Implement sign up form component¶
To complete the sign-up flow, we need to create a sign-up form component that will call the API route we created at app/api/signup/route.ts
. Below is an example implementation of the SignUpForm
client-side component:
"use client";
export default function SignUpForm() {
async function handleSignUp(formData: FormData) {
// Call API route using fetch
const res = await fetch("/api/signup", {
method: "POST",
body: formData,
});
// Handle response
if (res.ok) {
// Handle successful sign-up
console.log("User registered successfully!");
} else {
// Handle errors
const errorData = await res.json();
console.error("Error:", errorData.error);
}
}
return (
<form onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target);
handleSignUp(formData);
}}>
{/* Sign up form content */}
<input type="email" name="email" placeholder="Email" required />
<input type="password" name="password" placeholder="Password" required />
<input type="text" name="firstName" placeholder="First Name" required />
<input type="text" name="lastName" placeholder="Last Name" required />
<button type="submit">Sign Up</button>
</form>
);
}
This component includes a form with fields for email, password, first name, and last name. When the form is submitted, it calls the handleSignUp
function, which sends the form data to the API route for processing.
Info
Read more on: - Managing Users with SCIM - OAuth 2.0 Client Credentials Grant
Note
Refer to Step 1 of the Github sample app repository for the complete implementation.