Generate FSA Code with AI in Minutes
Input this prompt in your IDE to analyze your existing code base and generate FSA implementation code accordingly.
Compatible with Cursor, Windsurf, VS Code, and any AI-powered IDE
Get up and running with Scalekit’s Full Stack Authentication in under 10 minutes. You’ll build a complete authentication flow with hosted login and sign up pages, user session management, and secure logout - all optimized for B2B SaaS applications.
By the end of this guide, your application will have:
Scalekit handles the complex authentication flow while you focus on your core product:
Let’s get started!
Generate FSA Code with AI in Minutes
Input this prompt in your IDE to analyze your existing code base and generate FSA implementation code accordingly.
Compatible with Cursor, Windsurf, VS Code, and any AI-powered IDE
npm install @scalekit-sdk/node
pip install scalekit-sdk-python
go 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:2.0.1"
<!-- Maven users - add the following to your `pom.xml` --><dependency> <groupId>com.scalekit</groupId> <artifactId>scalekit-sdk-java</artifactId> <version>2.0.1</version></dependency>
Copy your API credentials from the Scalekit dashboard’s API Config section and set them as environment variables.
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.
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);
from scalekit import ScalekitClientimport os
scalekit = ScalekitClient( os.environ.get('SCALEKIT_ENVIRONMENT_URL'), os.environ.get('SCALEKIT_CLIENT_ID'), os.environ.get('SCALEKIT_CLIENT_SECRET'))
package main
import ( "os" "github.com/scalekit-inc/scalekit-sdk-go")
scalekit := scalekit.NewScalekitClient( os.Getenv("SCALEKIT_ENVIRONMENT_URL"), os.Getenv("SCALEKIT_CLIENT_ID"), os.Getenv("SCALEKIT_CLIENT_SECRET"),)
import com.scalekit.ScalekitClient;
public class Auth { public ScalekitClient createScalekitClient() { return new ScalekitClient( System.getenv("SCALEKIT_ENVIRONMENT_URL"), System.getenv("SCALEKIT_CLIENT_ID"), System.getenv("SCALEKIT_CLIENT_SECRET") ); }}
Generate the authorization URL by passing a registered callback URL and scopes to the Scalekit SDK.
curl -G -v --location "<SCALEKIT_ENVIRONMENT_URL>/oauth/authorize" \--data-urlencode 'grant_type=authorization_code' \--data-urlencode "organization_id={organization_id}" \--data-urlencode "response_type=code" \--data-urlencode "scope=openid profile" \--data-urlencode 'redirect_uri=https://your-app.com/callback' \--data-urlencode "client_id={client_id}" \--data-urlencode "state=random_state_value"
const redirectUri = '<http://localhost:3000/api/callback>';const options = { scopes: ['openid', 'profile', 'email', 'offline_access']};
const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options);
res.redirect(authorizationUrl);
from scalekit import AuthorizationUrlOptions
redirect_uri = 'http://localhost:3000/api/callback'options = AuthorizationUrlOptions( 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)
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 appropriately panic(err)}
// For web frameworks like Gin:// c.Redirect(http.StatusFound, authorizationUrl.String())
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.
After users authenticate, Scalekit redirects them back to your registered callback URL with an authorization code. Exchange this code to get the user’s profile and session tokens:
curl -v --location "<SCALEKIT_ENVIRONMENT_URL>/oauth/token" \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'grant_type=authorization_code' \--data-urlencode "code={authorization_code}" \--data-urlencode 'redirect_uri=https://your-app.com/callback' \--data-urlencode "client_id={client_id}" \--data-urlencode "client_secret={client_secret}"
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 profile const 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 lines
res.redirect('/dashboard/profile'); } catch (err) { console.error('Error exchanging code:', err); res.status(500).json({ error: 'Failed to authenticate user' }); }});
6 collapsed lines
from flask import Flask, request, redirect, jsonifyfrom scalekit import ScalekitClient, CodeAuthenticationOptions
app = Flask(__name__)# scalekit imported from your auth utils
redirect_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}), 401
try: # Exchange the authorization code for a user profile options = 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 lines
return redirect('/dashboard/profile') except Exception as err: print(f'Error exchanging code: {err}') return jsonify({'error': 'Failed to authenticate user'}), 500
9 collapsed lines
package main
import ( "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 profile options := 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 user c.Redirect(http.StatusFound, "/dashboard/profile")}
10 collapsed lines
import 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) { // handle error }
try { // Exchange the authorization code for a user profile AuthenticationOptions options = new AuthenticationOptions(); AuthenticationResponse authResult = scalekit .authentication() .authenticateWithCode(code,redirectUri,options);
var user = authResult.getIdTokenClaims();
// "user" object contains the user's profile information // Next step: Create a session and log in the user return new RedirectView("/dashboard/profile");
} catch (Exception err) {4 collapsed lines
// Handle exception (e.g., log error, return error response) } }}
The authenticateWithCode
method returns an object containing the user’s profile information (user
object) and session tokens including idToken
, accessToken
and refreshToken
.
{"access_token": "eyJhImtpZCI6InNu2NiIsInR5cCI6IkpXVCJ9...","token_type": "Bearer","expires_in": 86399,"scope": "openid profile","id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InNuIsInR5cCI6IkpXVCJ9..."}
{ 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.
Now that you’ve the entire user’s profile details including their email address, verification status, create a secure session by storing the session tokens. Use encrypted HTTP-only cookies for the access token and securely store the refresh token:
import cookieParser from 'cookie-parser';// Set cookie parser middlewareapp.use(cookieParser());
// encrypt the accessToken using a secure encryption algorithmconst encryptedAccessToken = encrypt(authResult.accessToken);
// setting up accessToken as HTTP-only cookieres.cookie('accessToken', encryptedAccessToken, { maxAge: (authResult.expiresIn - 60) * 1000, httpOnly: true, secure: true, path: '/', sameSite: 'strict'});
// Store the refreshToken in a secure place
from flask import Flask, make_responsefrom cryptography.fernet import Fernetimport os
# Cookie parsing is built-in with Flask's request objectapp = Flask(__name__)
# encrypt the accessToken using a secure encryption algorithmkey = os.environ.get('ENCRYPTION_KEY') # Store securelycipher_suite = Fernet(key)encrypted_access_token = cipher_suite.encrypt(authResult['accessToken'].encode()).decode()
// setting up accessToken as HTTP-only cookieresponse = make_response()response.set_cookie( 'accessToken', encrypted_access_token, max_age=(authResult['expiresIn'] - 60) * 1000, httponly=True, secure=True, path='/', samesite='Strict')
// Store the refreshToken in a secure place
import ( "crypto/aes" "crypto/cipher" "net/http" "os")
// encrypt the accessToken using a secure encryption algorithmkey := []byte(os.Getenv("ENCRYPTION_KEY"))block, _ := aes.NewCipher(key)gcm, _ := cipher.NewGCM(block)encryptedAccessToken, _ := encrypt(authResult.AccessToken)
// setting up accessToken as HTTP-only cookiecookie := &http.Cookie{ Name: "accessToken", Value: encryptedAccessToken, MaxAge: (authResult.ExpiresIn - 60) * 1000, HttpOnly: true, Secure: true, Path: "/", SameSite: http.SameSiteStrictMode,}http.SetCookie(w, cookie)
// Store the refreshToken in a secure place
import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletResponse;import java.util.Base64;
// setting up accessToken as HTTP-only cookieString encryptedRefreshToken = encrypt(authResult.getAccessToken());Cookie accessTokenCookie = new Cookie("accessToken", encryptedAccessToken);accessTokenCookie.setMaxAge((authResult.getExpiresIn() - 60) * 1000);accessTokenCookie.setHttpOnly(true);accessTokenCookie.setSecure(true);accessTokenCookie.setPath("/");response.addCookie(accessTokenCookie);
// setting up refreshToken as HTTP-only cookieString encryptedRefreshToken = encrypt(authResult.getRefreshToken());Cookie refreshTokenCookie = new Cookie("refreshToken", encryptedRefreshToken);refreshTokenCookie.setHttpOnly(true);refreshTokenCookie.setSecure(true);refreshTokenCookie.setPath("/");response.addCookie(refreshTokenCookie);
// Store the refreshToken in a secure place
This sets browser cookies with the session tokens. Every request to your backend needs to verify the accessToken
to ensure the user is authenticated. If expired, use the refreshToken
to get a new access token.
// Middleware to verify and refresh tokens if neededconst verifyToken = async (req, res, next) => { try { // Get access token from cookie const accessToken = req.cookies.accessToken;
// Decrypt the accessToken using the same encryption algorithm const decryptedAccessToken = decrypt(accessToken);
if (!accessToken) { return res.status(401).json({ message: 'No access token provided' }); }
// Use Scalekit SDK to validate the token const isValid = await scalekit.validateAccessToken(decryptedAccessToken);
if (!isValid) { // Use stored refreshToken to get a new access token const { user, idToken, accessToken, refreshToken: newRefreshToken, } = await scalekit.refreshAccessToken(refreshToken);
// Store the new refresh token // Update the cookie with the new access token } next();};
// Example of using the middleware to protect routesapp.get('/dashboard', verifyToken, (req, res) => { // The user object is now available in req.user res.json({ message: 'This is a protected route', user: req.user });});
from functools import wrapsfrom flask import request, jsonify, make_response
def verify_token(f): """Decorator to verify and refresh tokens if needed""" @wraps(f) def decorated_function(*args, **kwargs): try: # Get access token from cookie access_token = request.cookies.get('accessToken')
if not access_token: return jsonify({'message': 'No access token provided'}), 401
# Decrypt the accessToken using the same encryption algorithm decrypted_access_token = decrypt(access_token)
# Use Scalekit SDK to validate the token is_valid = scalekit.validate_access_token(decrypted_access_token)
if not is_valid: # Get stored refresh token refresh_token = get_stored_refresh_token()
if not refresh_token: return jsonify({'message': 'No refresh token available'}), 401
# Use stored refreshToken to get a new access token token_response = scalekit.refresh_access_token(refresh_token)
# Python SDK returns dict with access_token and refresh_token new_access_token = token_response.get('access_token') new_refresh_token = token_response.get('refresh_token')
# Store the new refresh token store_refresh_token(new_refresh_token)
# Update the cookie with the new access token encrypted_new_access_token = encrypt(new_access_token) response = make_response(f(*args, **kwargs)) response.set_cookie( 'accessToken', encrypted_new_access_token, httponly=True, secure=True, path='/', samesite='Strict' )
return response
# If the token was valid we just invoke the view as-is return f(*args, **kwargs)
except Exception as e: return jsonify({'message': f'Token verification failed: {str(e)}'}), 401
return decorated_function
# Example of using the decorator to protect routes@app.route('/dashboard')@verify_tokendef dashboard(): return jsonify({ 'message': 'This is a protected route', 'user': getattr(request, 'user', None) })
import ( "context" "net/http")
func verifyToken(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // Get access token from cookie cookie, err := r.Cookie("accessToken") if err != nil { http.Error(w, `{"message": "No access token provided"}`, http.StatusUnauthorized) return }
accessToken := cookie.Value
// Decrypt the accessToken decryptedAccessToken, err := decrypt(accessToken) if err != nil { http.Error(w, `{"message": "Token decryption failed"}`, http.StatusUnauthorized) return }
// Use Scalekit SDK to validate the token isValid, err := scalekit.ValidateAccessToken(decryptedAccessToken) if err != nil || !isValid { // Get stored refresh token refreshToken, err := getStoredRefreshToken(r) if err != nil { http.Error(w, `{"message": "No refresh token available"}`, http.StatusUnauthorized) return }
// Use stored refreshToken to get a new access token tokenResponse, err := scalekit.RefreshAccessToken(refreshToken) if err != nil { http.Error(w, `{"message": "Token refresh failed"}`, http.StatusUnauthorized) return }
// Go SDK returns TokenResponse with AccessToken, RefreshToken, ExpiresIn // Store the new refresh token err = storeRefreshToken(tokenResponse.RefreshToken) if err != nil { http.Error(w, `{"message": "Failed to store refresh token"}`, http.StatusInternalServerError) return }
// Update the cookie with the new access token encryptedNewAccessToken, err := encrypt(tokenResponse.AccessToken) if err != nil { http.Error(w, `{"message": "Token encryption failed"}`, http.StatusInternalServerError) return }
newCookie := &http.Cookie{ Name: "accessToken", Value: encryptedNewAccessToken, HttpOnly: true, Secure: true, Path: "/", SameSite: http.SameSiteStrictMode, } http.SetCookie(w, newCookie)
r = r.WithContext(context.WithValue(r.Context(), "tokenValid", true)) } else { r = r.WithContext(context.WithValue(r.Context(), "tokenValid", true)) }
next(w, r) }}
// Example of using the middleware to protect routesfunc dashboardHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(`{ "message": "This is a protected route", "tokenValid": true }`))}
// Usage: http.HandleFunc("/dashboard", verifyToken(dashboardHandler))
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.Cookie;import org.springframework.web.servlet.HandlerInterceptor;
@Componentpublic class TokenVerificationInterceptor implements HandlerInterceptor { @Override public boolean preHandle( HttpServletRequest request, HttpServletResponse response, Object handler ) throws Exception { try { // Get access token from cookie String accessToken = getCookieValue(request, "accessToken"); String refreshToken = getCookieValue(request, "refreshToken");
// Decrypt the tokens String decryptedAccessToken = decrypt(accessToken); String decryptedRefreshToken = decrypt(refreshToken);
// Use Scalekit SDK to validate the token boolean isValid = scalekit.authentication().validateAccessToken(decryptedAccessToken);
// Use refreshToken to get a new access token AuthenticationResponse tokenResponse = scalekit .authentication() .refreshToken(decryptedRefreshToken);
// Update the cookie with the new access token and refresh token String encryptedNewAccessToken = encrypt(tokenResponse.getAccessToken()); String encryptedNewRefreshToken = encrypt(tokenResponse.getRefreshToken());
Cookie accessTokenCookie = new Cookie("accessToken", encryptedNewAccessToken); accessTokenCookie.setHttpOnly(true); accessTokenCookie.setSecure(true); accessTokenCookie.setPath("/"); response.addCookie(accessTokenCookie);
Cookie refreshTokenCookie = new Cookie("refreshToken", encryptedNewRefreshToken); refreshTokenCookie.setHttpOnly(true); refreshTokenCookie.setSecure(true); refreshTokenCookie.setPath("/"); response.addCookie(refreshTokenCookie);
return true; } catch (Exception e) { // handle exception } }
private String getCookieValue(HttpServletRequest request, String cookieName) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } } return null; }}
Successfully authenticated users can now access your dashboard.
To properly log out users, clear local session data and invalidate their session on Scalekit’s servers:
/** * Handles user logout by: * 1. Clearing local session data * 2. Invalidating the Scalekit session * 3. Redirecting to post-logout URL */app.get('/logout', (req, res) => { // Clear all session data including cookies and local storage clearSessionData();
/** * Generates a Scalekit logout URL that will: * - Invalidate the user's session on Scalekit's servers * - Redirect the user to the specified post-logout URL * @param {string} idTokenHint - The user's ID token to invalidate * @param {string} postLogoutRedirectUri - URL to redirect after logout * @returns {string} The complete logout URL */ const logoutUrl = scalekit.getLogoutUrl( idTokenHint, postLogoutRedirectUri );
// Redirect to Scalekit's logout endpoint // Note: This is a one-time use URL that becomes invalid after use res.redirect(logoutUrl);});
from flask import Flask, redirectfrom scalekit import LogoutUrlOptions
app = Flask(__name__)
@app.route('/logout')def logout(): # Clear all session data including cookies and local storage clear_session_data()
# Generate Scalekit logout URL options = LogoutUrlOptions( id_token_hint=id_token, post_logout_redirect_uri=post_logout_redirect_uri ) logout_url = scalekit.get_logout_url(options)
# Redirect to Scalekit's logout endpoint # Note: This is a one-time use URL that becomes invalid after use return redirect(logout_url)
package main
import ( "net/http" "github.com/gin-gonic/gin" "github.com/scalekit-inc/scalekit-sdk-go")
func logoutHandler(c *gin.Context) { // Clear all session data including cookies and local storage clearSessionData()
// Generate Scalekit logout URL options := scalekit.LogoutUrlOptions{ IdTokenHint: idToken, PostLogoutRedirectUri: postLogoutRedirectUri, } logoutUrl, err := scalekit.GetLogoutUrl(options) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to generate logout URL", }) return }
// Redirect to Scalekit's logout endpoint // Note: This is a one-time use URL that becomes invalid after use c.Redirect(http.StatusFound, logoutUrl.String())}
import com.scalekit.internal.http.LogoutUrlOptions;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.view.RedirectView;import java.net.URL;
@RestControllerpublic class LogoutController {
@GetMapping("/logout") public RedirectView logout() { // Clear all session data including cookies and local storage clearSessionData();
// Generate Scalekit logout URL LogoutUrlOptions options = new LogoutUrlOptions(); options.setIdTokenHint(idToken); options.setPostLogoutRedirectUri(postLogoutRedirectUri);
URL logoutUrl = scalekit.authentication() .getLogoutUrl(options);
// Redirect to Scalekit's logout endpoint // Note: This is a one-time use URL that becomes invalid after use return new RedirectView(logoutUrl.toString()); }}
The logout process completes when Scalekit invalidates the user’s session and redirects them to your specified post-logout URL.
🎉 Congratulations! You’ve successfully integrated Scalekit’s Full Stack Authentication. Your application now has:
You’ve completed the Scalekit quickstart and enabled secure authentication for your users and sign-in flow.