Skip to content

Complete login with code exchange

Process authentication callbacks and handle redirect flows after users authenticate with Scalekit

Once users have successfully verified their identity using their chosen login method, Scalekit will have gathered the necessary user information for your app to complete the login process. However, your app must provide a callback endpoint where Scalekit can exchange an authorization code to return your app the user details.

  1. Validate the state parameter recommended

    Section titled “Validate the state parameter ”

    Before exchanging the authorization code, your application must validate the state parameter returned by Scalekit. Compare it with the value you stored in the user’s session before redirecting them. This critical step prevents Cross-Site Request Forgery (CSRF) attacks, ensuring the authentication response corresponds to a request initiated by the same user.

    Validate state in Express.js
    const { state } = req.query;
    // Assumes you are using a session middleware like express-session
    const storedState = req.session.oauthState;
    delete req.session.oauthState; // State should be used only once
    if (!state || state !== storedState) {
    console.error('Invalid state parameter');
    return res.redirect('/login?error=invalid_state');
    }
  2. Once the state is validated, your app can safely exchange the authorization code for tokens. The Scalekit SDK simplifies this process with the authenticateWithCode method, which handles the secure server-to-server request.

    Express.js callback handler
    app.get('/auth/callback', async (req, res) => {
    const { code, error, error_description, state } = req.query;
    // Add state validation here (see previous step)
    11 collapsed lines
    // Handle errors first
    if (error) {
    console.error('Authentication error:', error);
    return res.redirect('/login?error=auth_failed');
    }
    if (!code) {
    return res.redirect('/login?error=missing_code');
    }
    try {
    // Exchange code for user data
    const authResult = await scalekit.authenticateWithCode(
    code,
    'https://yourapp.com/auth/callback'
    );
    const { user, accessToken, refreshToken } = authResult;
    11 collapsed lines
    // TODO: Store user session (next guide covers this)
    // req.session.user = user;
    res.redirect('/dashboard');
    } catch (error) {
    console.error('Token exchange failed:', error);
    res.redirect('/login?error=exchange_failed');
    }
    });

    The authorization code can be redeemed only once and expires in approx ~10 minutes. Reuse or replay attempts typically return errors like invalid_grant. If this occurs, start a new login flow to obtain a fresh code and state.

    The authResult object returned contains:

    {
    user: {
    email: "john.doe@example.com",
    emailVerified: true,
    givenName: "John",
    name: "John Doe",
    id: "usr_74599896446906854"
    },
    idToken: "eyJhbGciO..", // Decode for full user details
    accessToken: "eyJhbGciOi..",
    refreshToken: "rt_8f7d6e5c4b3a2d1e0f9g8h7i6j..",
    expiresIn: 299 // in seconds
    }
    KeyDescription
    userCommon user details with email, name, and verification status
    idTokenJWT containing verified full user identity claims
    accessTokenShort-lived token that determines current access
    refreshTokenLong-lived token to obtain new access tokens
  3. The idToken and accessToken are JSON Web Tokens (JWT) that contain user claims. These tokens can be decoded to retrieve comprehensive user and access information.

    Decode ID token
    // Use a library like 'jsonwebtoken'
    const jwt = require('jsonwebtoken');
    // The idToken from the authResult object
    const { idToken } = authResult;
    // Decode the token without verifying its signature
    const decoded = jwt.decode(idToken);
    console.log('Decoded claims:', decoded);

    The decoded token claims contain:

    ID token decoded
    {
    "at_hash": "ec_jU2ZKpFelCKLTRWiRsg", // Access token hash for validation
    "aud": [
    "skc_58327482062864390" // Audience (your client ID)
    ],
    "azp": "skc_58327482062864390", // Authorized party (your client ID)
    "c_hash": "6wMreK9kWQQY6O5R0CiiYg", // Authorization code hash
    "client_id": "skc_58327482062864390", // Your application's client ID
    "email": "john.doe@example.com", // User's email address
    "email_verified": true, // Whether the user's email is verified
    "exp": 1742975822, // Expiration time (Unix timestamp)
    "family_name": "Doe", // User's last name
    "given_name": "John", // User's first name
    "iat": 1742974022, // Issued at time (Unix timestamp)
    "iss": "https://scalekit-z44iroqaaada-dev.scalekit.cloud", // Issuer (Scalekit environment URL)
    "name": "John Doe", // User's full name
    "oid": "org_59615193906282635", // Organization ID
    "sid": "ses_65274187031249433", // Session ID
    "sub": "usr_63261014140912135" // Subject (user's unique ID)
    }

An IdToken contains comprehensive profile information about the user. You can save this in your database for app use cases, using your own identifier. Now, let’s utilize access and refresh tokens to manage user access and maintain active sessions.