Restricted access through invite-only sign ups
Build applications that enable organization owners to invite users to join their organization. Scalekit takes care of sending the invite emails, verifying their email addresses, and creating the user accounts end to end.
Invite-only signup is ideal for the following scenarios:
- Closed beta applications: You want to control who can access your application during the beta phase.
- Enterprise applications: Organization admins need to invite team members to join their workspace.
- B2B SaaS platforms: You want to restrict access to invited users only
- Exclusive communities: Applications that require invitation-based membership.
Scalekit helps you implement invite-only signup flows while handling the complexity of user management and authentication.
Set up invite-only signup page with Scalekit
Section titled “Set up invite-only signup page with Scalekit”You can implement invite-only signup by configuring Scalekit to disable public signups and using the invitation flow. This ensures that only invited users can create accounts.
-
Configure your application for invite-only access
Section titled “Configure your application for invite-only access”npm install @scalekit-sdk/nodepip install scalekit-sdk-pythongo get -u github.com/scalekit-inc/scalekit-sdk-go/* Gradle users - add the following to your dependencies in build file */implementation "com.scalekit:scalekit-sdk-java:1.1.3"<!-- Maven users - add the following to your `pom.xml` --><dependency><groupId>com.scalekit</groupId><artifactId>scalekit-sdk-java</artifactId><version>1.1.3</version></dependency>Copy your API credentials from the Scalekit dashboard’s API Config section and set them as environment variables.
Terminal window SCALEKIT_ENVIRONMENT_URL='<YOUR_ENVIRONMENT_URL>'SCALEKIT_CLIENT_ID='<ENVIRONMENT_CLIENT_ID>'SCALEKIT_CLIENT_SECRET='<ENVIRONMENT_CLIENT_SECRET>'Create a new Scalekit client instance after initializing the environment variables.
utils/auth.js import { Scalekit } from '@scalekit-sdk/node';export let scalekit = new Scalekit(process.env.SCALEKIT_ENVIRONMENT_URL,process.env.SCALEKIT_CLIENT_ID,process.env.SCALEKIT_CLIENT_SECRET);utils/auth.py from scalekit import ScalekitClient, AuthorizationUrlOptions, CodeAuthenticationOptionsimport osscalekit = ScalekitClient(os.environ.get('SCALEKIT_ENVIRONMENT_URL'),os.environ.get('SCALEKIT_CLIENT_ID'),os.environ.get('SCALEKIT_CLIENT_SECRET'))utils/auth.go package mainimport ("os""github.com/scalekit-inc/scalekit-sdk-go")var scalekit, err = scalekit.NewScalekitClient(os.Getenv("SCALEKIT_ENVIRONMENT_URL"),os.Getenv("SCALEKIT_CLIENT_ID"),os.Getenv("SCALEKIT_CLIENT_SECRET"),)if err != nil {panic(err)}utils/Auth.java import com.scalekit.ScalekitClient;import com.scalekit.Environment;public class Auth {public static ScalekitClient scalekit;static {Environment.configure(System.getenv("SCALEKIT_ENVIRONMENT_URL"),System.getenv("SCALEKIT_CLIENT_ID"),System.getenv("SCALEKIT_CLIENT_SECRET"));scalekit = new ScalekitClient(System.getenv("SCALEKIT_ENVIRONMENT_URL"),System.getenv("SCALEKIT_CLIENT_ID"),System.getenv("SCALEKIT_CLIENT_SECRET"));}} -
Redirect users to the sign-in page
Section titled “Redirect users to the sign-in page”Generate the authorization URL by passing a registered callback URL and scopes to the Scalekit SDK. Note that we don’t include the
prompt: 'create'
parameter since public signup is disabled.Express.js const redirectUri = 'http://localhost:3000/api/callback';const options = {scopes: ['openid', 'profile', 'email', 'offline_access'],};const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options);res.redirect(authorizationUrl);Flask from scalekit import AuthorizationUrlOptionsredirect_uri = 'http://localhost:3000/api/callback'options = AuthorizationUrlOptions()options.scopes = ['openid', 'profile', 'email', 'offline_access']authorization_url = scalekit.get_authorization_url(redirect_uri, options)# For web frameworks like Flask/Django:# return redirect(authorization_url)Gin redirectUri := "http://localhost:3000/api/callback"options := scalekit.AuthorizationUrlOptions{Scopes: []string{"openid", "profile", "email", "offline_access"},}authorizationUrl, err := scalekit.GetAuthorizationUrl(redirectUri, options)if err != nil {// handle error appropriatelypanic(err)}// For web frameworks like Gin:// c.Redirect(http.StatusFound, authorizationUrl.String())Spring import com.scalekit.internal.http.AuthorizationUrlOptions;import java.net.URL;import java.util.Arrays;String redirectUri = "http://localhost:3000/api/callback";AuthorizationUrlOptions options = new AuthorizationUrlOptions();options.setScopes(Arrays.asList("openid", "profile", "email", "offline_access"));URL authorizationUrl = scalekit.authentication().getAuthorizationUrl(redirectUri, options);This will redirect the user to Scalekit’s managed sign-in page without signup options.
-
Retrieve user profile after authentication
Section titled “Retrieve user profile after authentication”Scalekit triggers a callback to your registered callback URL with an authorization code. Exchange the code to get the user’s profile information.
import scalekit from '@/utils/auth.js'const redirectUri = 'http://localhost:3000/api/callback';// Get the authorization code from the scalekit initiated callbackapp.get('/api/callback', async (req, res) => {const { code, error, error_description } = req.query;if (error) {return res.status(401).json({ error, error_description });}try {// Exchange the authorization code for a user profileconst authResult = await scalekit.authenticateWithCode(code, redirectUri);const { user } = authResult;// "user" object contains the user's profile information// Next step: Create a session and log in the user5 collapsed linesres.redirect('/dashboard/profile');} catch (err) {console.error('Error exchanging code:', err);res.status(500).json({ error: 'Failed to authenticate user' });}});6 collapsed linesfrom flask import Flask, request, redirect, jsonifyfrom scalekit import ScalekitClient, CodeAuthenticationOptionsapp = Flask(__name__)# scalekit imported from your auth utilsredirect_uri = 'http://localhost:3000/api/callback'@app.route('/api/callback')def callback():code = request.args.get('code')error = request.args.get('error')error_description = request.args.get('error_description')if error:return jsonify({'error': error, 'error_description': error_description}), 401try:# Exchange the authorization code for a user profileoptions = CodeAuthenticationOptions()auth_result = scalekit.authenticate_with_code(code, redirect_uri, options)user = auth_result.user# "user" object contains the user's profile information# Next step: Create a session and log in the user4 collapsed linesreturn redirect('/dashboard/profile')except Exception as err:print(f'Error exchanging code: {err}')return jsonify({'error': 'Failed to authenticate user'}), 5009 collapsed linespackage mainimport ("log""net/http""os""github.com/gin-gonic/gin""github.com/scalekit-inc/scalekit-sdk-go")// Create Scalekit client instancevar scalekitClient = scalekit.NewScalekitClient(os.Getenv("SCALEKIT_ENVIRONMENT_URL"),os.Getenv("SCALEKIT_CLIENT_ID"),os.Getenv("SCALEKIT_CLIENT_SECRET"),)const redirectUri = "http://localhost:3000/api/callback"func callbackHandler(c *gin.Context) {code := c.Query("code")errorParam := c.Query("error")errorDescription := c.Query("error_description")if errorParam != "" {c.JSON(http.StatusUnauthorized, gin.H{"error": errorParam,"error_description": errorDescription,})return}// Exchange the authorization code for a user profileoptions := scalekit.AuthenticationOptions{}authResult, err := scalekitClient.AuthenticateWithCode(code, redirectUri, options,)if err != nil {log.Printf("Error exchanging code: %v", err)c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to authenticate user",})return}user := authResult.User// "user" object contains the user's profile information// Next step: Create a session and log in the userc.Redirect(http.StatusFound, "/dashboard/profile")}10 collapsed linesimport com.scalekit.ScalekitClient;import com.scalekit.internal.http.AuthenticationOptions;import com.scalekit.internal.http.AuthenticationResponse;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.view.RedirectView;import org.springframework.http.ResponseEntity;import org.springframework.http.HttpStatus;import java.util.HashMap;import java.util.Map;@RestControllerpublic class CallbackController {private final String redirectUri = "http://localhost:3000/api/callback";@GetMapping("/api/callback")public Object callback(@RequestParam(required = false) String code,@RequestParam(required = false) String error,@RequestParam(name = "error_description", required = false) String errorDescription) {if (error != null) {Map<String, String> errorResponse = new HashMap<>();errorResponse.put("error", error);errorResponse.put("error_description", errorDescription);return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse);}try {// Exchange the authorization code for a user profileAuthenticationOptions options = new AuthenticationOptions();AuthenticationResponse authResult = scalekit.authentication().authenticateWithCode(code, redirectUri, options);var user = authResult.getUser();// "user" object contains the user's profile information// Next step: Create a session and log in the userreturn new RedirectView("/dashboard/profile");8 collapsed lines} catch (Exception err) {System.err.println("Error exchanging code: " + err.getMessage());Map<String, String> errorResponse = new HashMap<>();errorResponse.put("error", "Failed to authenticate user");return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);}}}The
authenticateWithCode
method returns an object containing the user’s profile information (user
object) andidToken
(JWT).{user: {email: "john.doe@example.com",emailVerified: true,givenName: "John",name: "John Doe",id: "usr_74599896446906854"},idToken: "eyJhbGciO..",accessToken: "eyJhbGciOi..",refreshToken: "rt_8f7d6e5c4b3a2d1e0f9g8h7i6j..",expiresIn: 299}{"at_hash": "ec_jU2ZKpFelCKLTRWiRsg","aud": ["skc_58327482062864390"],"azp": "skc_58327482062864390","c_hash": "6wMreK9kWQQY6O5R0CiiYg","client_id": "skc_58327482062864390","email": "john.doe@example.com","email_verified": true,"exp": 1742975822,"family_name": "Doe","given_name": "John","iat": 1742974022,"iss": "https://scalekit-z44iroqaaada-dev.scalekit.cloud","name": "John Doe","oid": "org_59615193906282635","sid": "ses_65274187031249433","sub": "usr_63261014140912135"}You can decode the
idToken
to access user information like email, name, and profile verification status directly from the token claims.
With these configurations, your B2B application now has a sign-in page but no public signup option, ensuring that only invited users can access your application. After successful authentication, you can proceed to create a session.
Invite-only sign up using Scalekit SDK
Section titled “Invite-only sign up using Scalekit SDK”For applications where you want to build custom invitation flows in your own UI, Scalekit provides APIs to programmatically invite users. This is ideal when organization admins or workspace owners need to invite team members directly from your application’s dashboard.
Common use cases include:
- Admin dashboards: Organization admins can invite users from a settings or team management page.
- Bulk invitations: Import and invite multiple users at once from CSV files or directory systems.
- Custom workflows: Implement approval processes or conditional invitations based on business logic.
- Integration with existing systems: Connect invitation flows with your CRM, HR systems, or user directories.
-
Create user invitations programmatically
Section titled “Create user invitations programmatically”To invite a user to an organization, create a user membership with their email address and the target organization ID. Scalekit handles sending the invitation email and managing the signup process.
const newUser = await scalekit.user.createUserAndMembership('org_xxxxxxxxxxxx', {email: "user@example.com",externalId: "crm-user-87425",userProfile: {firstName: "John",lastName: "Doe"},metadata: {plan: "free",department: "Engineering"},sendInvitationEmail: true});from scalekit import ScalekitClientfrom scalekit.v1.users.users_pb2 import CreateUser, CreateUserProfile# Initialize scalekit clientscalekit = ScalekitClient(<'SCALEKIT_ENV_URL'>,<'SCALEKIT_CLIENT_ID'>,<'SCALEKIT_CLIENT_SECRET'>)# Create user objectuser = CreateUser(email="user@example.com",external_id="crm-user-87425",user_profile=CreateUserProfile(first_name="John",last_name="Doe"),metadata={"plan": "free","department": "Engineering"})# Create the usernew_user = scalekit.users.create_user_and_membership(organization_id='org_xxxxxxxxxxxx', user=user)import ("context"usersv1 "github.com/scalekit-inc/scalekit-sdk-go/pkg/grpc/scalekit/v1/users")user := &usersv1.CreateUser{Email: "user@example.com",ExternalId: "crm-user-87425",UserProfile: &usersv1.CreateUserProfile{FirstName: "John",LastName: "Doe",},Metadata: map[string]string{"plan": "free","department": "Engineering",},}newUser, err := scalekit.User().CreateUserAndMembership(context.Background(),"org_xxxxxxxxxxxx", // The ID of the organization the user belongs touser,false, // sendInvitationEmail)if err != nil {// handle error appropriatelyreturn err}import com.scalekit.grpc.scalekit.v1.users.*;CreateUserAndMembershipRequest request = CreateUserAndMembershipRequest.newBuilder().setUser(CreateUser.newBuilder().setEmail("user@example.com").setExternalId("crm-user-87425").setUserProfile(CreateUserProfile.newBuilder().setFirstName("John").setLastName("Doe").build()).putMetadata("plan", "free").putMetadata("department", "Engineering").build()).build();CreateUserAndMembershipResponse newUser = scalekit.users().createUserAndMembership("org_xxxxxxxxxxxx", request); // The ID of the organization the user belongs toKey parameters:
email
: The email address of the user to invite (required)organization_id
: The ID of the organization they’re joining (required)sendActivationEmail
: Set totrue
to automatically send invitation emails (recommended)roles
: Optional array of roles to assign to the invited usermetadata
: Optional custom data to associate with the membership
-
Handle invitation responses
Section titled “Handle invitation responses”When a user is successfully invited, Scalekit returns a user object with membership details. The membership status will be
INVITED
until the user accepts the invitation.Example invitation response {"user": {"id": "usr_01HTR0ABCXYZ","environmentId": "env_01HTQZ99MMNZ","createTime": "2025-06-19T15:41:22Z","updateTime": "2025-06-19T15:41:22Z","email": "user@example.com","externalId": "crm-user-87425","memberships": [{"organizationId": "org_xxxxxxxxxxxx","joinTime": "2025-06-19T15:41:22Z","membershipStatus": "ACTIVE","roles": [{"id": "role_admin","name": "admin"}],"primaryIdentityProvider": "IDENTITY_PROVIDER_UNSPECIFIED","metadata": {"plan": "free","department": "Engineering"}}],"userProfile": {"id": "prof_01HTR0PQRMNO","firstName": "John","lastName": "Doe","name": "John Doe","locale": "en","emailVerified": false,"phoneNumber": "","metadata": {},"customAttributes": {}},"metadata": {"plan": "free","department": "Engineering"},"lastLogin": null}} -
Handle user invitation acceptance
Section titled “Handle user invitation acceptance”When invited users click the invitation link in their email, Scalekit redirects them to your application’s registered initiate login endpoint. Your application then completes the authentication flow.
Set up the initiate login endpoint:
-
Register your endpoint in the Scalekit dashboard (for example,
https://your-app.com/auth/login/initiate
) -
Handle the redirect by constructing an authorization URL and redirecting the user to Scalekit’s hosted login page
-
Complete authentication when the user returns to your callback URL
Example endpoint implementation:
Express.js app.get('/auth/login/initiate', (req, res) => {const redirectUri = 'http://localhost:3000/api/callback';const options = {scopes: ['openid', 'profile', 'email', 'offline_access'],prompt: 'create'};const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options);res.redirect(authorizationUrl);});Flask @app.route('/auth/login/initiate')def initiate_login():redirect_uri = 'http://localhost:3000/api/callback'options = AuthorizationUrlOptions()optons.scopes = ['openid', 'profile', 'email', 'offline_access']options.prompt = 'create'authorization_url = scalekit.get_authorization_url(redirect_uri, options)return redirect(authorization_url)Gin func initiateLogin(c *gin.Context) {redirectUri := "http://localhost:3000/api/callback"options := scalekit.AuthorizationUrlOptions{Scopes: []string{"openid", "profile", "email", "offline_access"},Prompt: "create",}authorizationUrl, err := scalekit.GetAuthorizationUrl(redirectUri, options)if err != nil {c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})return}c.Redirect(http.StatusFound, authorizationUrl.String())}Spring @GetMapping("/auth/login/initiate")public ResponseEntity<Void> initiateLogin() {String redirectUri = "http://localhost:3000/api/callback";AuthorizationUrlOptions options = new AuthorizationUrlOptions();options.setScopes(Arrays.asList("openid", "profile", "email", "offline_access"));options.setPrompt("create");URL authorizationUrl = scalekit.authentication().getAuthorizationUrl(redirectUri, options);return ResponseEntity.status(HttpStatus.FOUND).header("Location", authorizationUrl.toString()).build();}Authentication flow:
- User clicks invitation link → Redirected to your initiate login endpoint
- Your endpoint redirects → User goes to Scalekit’s hosted login page
- User authenticates → Scalekit verifies their email and credentials
- Scalekit redirects back → User returns to your callback URL with authorization code
- Your app exchanges code → Retrieve user profile and create session
-
This programmatic approach gives you full control over the invitation experience while leveraging Scalekit’s robust user management and email delivery infrastructure.