Bring Your Own Auth
Using Scalekit as a drop-in OAuth 2.1 authorization layer for your MCP Servers with federated authentication to your existing auth layer.
Scalekit also offers the option to integrate your existing authentication infrastructure with Scalekit’s OAuth layer for MCP servers. Use this when you have an existing auth system and want to add MCP OAuth without migrating users.
When your B2B application already has an established authentication system, you can connect it to your MCP server through Scalekit. This ensures that:
- Users see the same familiar login screen whether accessing your application or your MCP server
- No user migration required - your existing user accounts work immediately with MCP
- You maintain control over your authentication logic while gaining MCP OAuth 2.1 compliance
This “bring your own auth” approach standardizes the authorization layer without requiring you to rebuild your existing authentication infrastructure from scratch.
Step-by-Step Workflow
Section titled “Step-by-Step Workflow”When an MCP client initiates an authentication flow, Scalekit redirects to your login endpoint. You then provide user details to Scalekit via a secure backend call, and finally redirect back to Scalekit to complete the process.
1. Initiate Authentication
Section titled “1. Initiate Authentication”- The MCP client starts the authentication flow by calling
/oauth/authorizeon Scalekit. - Scalekit redirects the user to your login endpoint, passing two parameters:
login_request_id: Unique identifier for the login request.state: Value to maintain state between requests.
https://app.example.com/login?login_request_id=lri_86659065219908156&state=HntJ_ENB6y161i9_P1yzuZVv2SSTfD3aZH-Tej0_Y33_Fk8Z3g2. Handle Authentication in Your Application
Section titled “2. Handle Authentication in Your Application”Once the user lands on your login page:
a. Authenticate the User
Section titled “a. Authenticate the User”Take the user through your regular authentication logic (e.g., username/password, SSO, etc.).
b. Send User Details to Scalekit
Section titled “b. Send User Details to Scalekit”Send the authenticated user’s profile details from your backend to Scalekit to complete the login handshake.
pip install scalekit-sdk-pythonfrom scalekit import ScalekitClientimport os
scalekit_client = ScalekitClient( os.environ.get('SCALEKIT_ENVIRONMENT_URL'), os.environ.get('SCALEKIT_CLIENT_ID'), os.environ.get('SCALEKIT_CLIENT_SECRET'))
# Update login user detailstry: response = scalekit_client.auth.update_login_user_details( connection_id="{{connection_id}}", login_request_id="{{login_request_id}}", user={ "sub": "1234567890", "email": "alice@example.com" }, )except Exception: # Handle the request/SDK failure (network error, invalid credentials, etc.) # and stop — do NOT redirect back to Scalekit on a failed request. raise
# response is a (message, grpc.Call) tuple; the auth request ID (req_xxx) is on the first element.auth_request_id = response[0].auth_request_id# Optional: paste auth_request_id into Scalekit Dashboard > Auth Logs# to view the entire auth journey for this login.
# To report a failed login instead, send login_failed=True (sub/email not required):# scalekit_client.auth.update_login_user_details(# connection_id="{{connection_id}}",# login_request_id="{{login_request_id}}",# user={"login_failed": True},# )npm install @scalekit-sdk/nodeimport { Scalekit } from '@scalekit-sdk/node';
// Initialize clientconst scalekit = new Scalekit( process.env.SCALEKIT_ENVIRONMENT_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET);
// Update login user detailslet res;try { res = await scalekit.auth.updateLoginUserDetails( '{{connection_id}}', // connectionId '{{login_request_id}}', // loginRequestId { sub: '1234567890', email: 'alice@example.com' } );} catch (err) { // Handle the request/SDK failure (network error, invalid credentials, etc.) // and stop — do NOT redirect back to Scalekit on a failed request. throw err;}
// The response carries the auth request ID (req_xxx).const authRequestId = res.authRequestId;// Optional: paste authRequestId into Scalekit Dashboard > Auth Logs// to view the entire auth journey for this login.
// To report a failed login instead, send loginFailed: true (sub/email not required):// await scalekit.auth.updateLoginUserDetails(// '{{connection_id}}',// '{{login_request_id}}',// { loginFailed: true }// );go get -u github.com/scalekit-inc/scalekit-sdk-goimport ( "context" "fmt" "github.com/scalekit-inc/scalekit-sdk-go/v2" "os")
// Get the connectionId from ScaleKit dashboard -> MCP Server -> Your Server -> User Info Post Url// eg. https://example.scalekit.dev/api/v1/connections/conn_70982106544698372/auth-requests/{{login_request_id}}/user// Your connectionId is conn_70982106544698372 in this examplefunc updateLoggedInUserDetails() error { scalekitClient := scalekit.NewScalekitClient( os.Getenv("SCALEKIT_ENVIRONMENT_URL"), os.Getenv("SCALEKIT_CLIENT_ID"), os.Getenv("SCALEKIT_CLIENT_SECRET"), ) resp, err := scalekitClient.Auth().UpdateLoginUserDetails(context.Background(), &scalekit.UpdateLoginUserDetailsRequest{ ConnectionId: "{{connection_id}}", LoginRequestId: "{{login_request_id}}", // this value is dynamic per login User: &scalekit.LoggedInUserDetails{ Sub: "1234567890", Email: "alice@example.com", }, }) if err != nil { return err }
// The response carries the auth request ID (req_xxx). authRequestId := resp.GetAuthRequestId() fmt.Println("auth request id:", authRequestId) // Optional: paste authRequestId into Scalekit Dashboard -> Auth Logs // to view the entire auth journey for this login.
// To report a failed login instead, send LoginFailed: true (Sub/Email not required): // _, err = scalekitClient.Auth().UpdateLoginUserDetails(context.Background(), &scalekit.UpdateLoginUserDetailsRequest{ // ConnectionId: "{{connection_id}}", // LoginRequestId: "{{login_request_id}}", // User: &scalekit.LoggedInUserDetails{LoginFailed: true}, // })
// Only if there is no error, perform the redirect to scalekit using the redirect url on your Scalekit Dashboard -> MCP Servers return nil}<dependency> <groupId>com.scalekit</groupId> <artifactId>scalekit-sdk-java</artifactId> <version>2.3.0</version></dependency>import com.scalekit.ScalekitClient;import com.scalekit.grpc.scalekit.v1.auth.User;import com.scalekit.internal.http.UpdateLoginUserDetailsResult;import com.scalekit.shaded.google.protobuf.Struct;import com.scalekit.shaded.google.protobuf.Value;
public class SendUserDetails {
// Returns the auth request ID (req_xxx) for the login request. public String sendUserDetails(String connectionId, String loginRequestId, boolean loginSucceeded) { ScalekitClient scalekitClient = new ScalekitClient( System.getenv("SCALEKIT_ENVIRONMENT_URL"), System.getenv("SCALEKIT_CLIENT_ID"), System.getenv("SCALEKIT_CLIENT_SECRET") );
User user; if (loginSucceeded) { Struct customAttributes = Struct.newBuilder() .putFields("access_level", Value.newBuilder().setNumberValue(101).build()) .putFields("subscription_type", Value.newBuilder().setStringValue("PREMIUM").build()) .build();
user = User.newBuilder() .setSub("user_123") .setEmail("test@example.com") .setName("Test User") .addRoles("support") .addRoles("developer") .setCustomAttributes(customAttributes) .build(); } else { // Report a failed login — sub/email are not required when loginFailed is true. user = User.newBuilder().setLoginFailed(true).build(); }
try { UpdateLoginUserDetailsResult result = scalekitClient.login().updateLoginUserDetails(connectionId, loginRequestId, user); // The auth request ID (req_xxx). Optionally store it to fetch events for this auth // request later, or paste it into Scalekit Dashboard > Auth Logs to view the entire // auth journey for this login. return result.getAuthRequestId(); } catch (Exception e) { // Handle the request/SDK failure (network error, invalid credentials, etc.) and // stop — do NOT redirect back to Scalekit on a failed request. throw new RuntimeException("Failed to update login user details", e); } }}Acquire an access_token before you could send user details by hitting the /oauth/token endpoint. You can get env_url, sk_client_id and sk_client_secret from Scalekit Dashboard > Settings
# --fail-with-body makes curl exit non-zero on an HTTP error so a failed token# request is distinguishable from a successful one.curl --fail-with-body --location '{{env_url}}/oauth/token' \--header 'Content-Type: application/x-www-form-urlencoded' \--data-urlencode 'grant_type=client_credentials' \--data-urlencode 'client_id={{sk_client_id}}' \--data-urlencode 'client_secret={{sk_client_secret}}'Scalekit responds with a JSON payload similar to:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIn0...", "token_type": "Bearer", "expires_in": 3600}Use the access_token in the Authorization header when making a machine-to-machine POST request to Scalekit with the user’s details.
# --fail-with-body makes curl exit non-zero (and still print the body) on an HTTP# error, so a failed request is distinguishable from a successful one.curl --fail-with-body --location '{{env_url}}/api/v1/connections/{{connection_id}}/auth-requests/{{login_request_id}}/user' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer {{access_token}}' \ --data-raw '{ "sub": "1234567890", "email": "alice@example.com", "roles": ["support", "developer"], "custom_attributes": { "access_level": 101, "subscription_type": "PREMIUM" } }'3. Redirect Back to Scalekit
Section titled “3. Redirect Back to Scalekit”- Once you receive a successful response from Scalekit, redirect the user back to Scalekit using the provided
statevalue to the below endpoint.
Example Redirect URL:
{{envurl}}/sso/v1/connections/{{connection_id}}/partner:callback?state={{state_value}}state_value should match the state parameter you received in Step 1.
4. Completion
Section titled “4. Completion”- After processing the callback from your auth system, Scalekit will handle the remaining steps (showing the consent screen to the user, token exchange, etc.) automatically.
Try out the BYOA MCP server: Clone the byoa-mcp-node sample — a working Node.js MCP server with a custom login page, Scalekit token validation, and custom claims flowing through to tool handlers. Follow the README to run it locally end-to-end.