Skip to content

Preserve the user's intended destination

Redirect users back to the page they asked for after authentication using a signed return URL.

Users may bookmark specific pages of your app, but their session might be expired. They need to be redirected to the page they asked for after authentication. That means your app needs to preserve the user’s original destination.

You will capture the user’s original destination, carry it through the OAuth flow safely, and redirect back after login. You will prevent open-redirect attacks by validating and signing the return URL.

  1. When an unauthenticated user requests a protected route, capture its path.

    Express.js
    app.get('/login', (req, res) => {
    const nextPath = typeof req.query.next === 'string' ? req.query.next : '/'
    // Only allow internal paths
    const safe = nextPath.startsWith('/') && !nextPath.startsWith('//') ? nextPath : '/'
    res.cookie('sk_return_to', safe, { httpOnly: true, secure: true, sameSite: 'lax', path: '/' })
    // build authorization URL next
    })
  2. Generate the authorization URL as in the quickstart. Optionally include a short hint in state like "n=/billing" after signing or encoding.

    Express.js
    const redirectUri = 'https://your-app.com/auth/callback'
    const options = { scopes: ['openid','profile','email','offline_access'] }
    const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, options)
    res.redirect(authorizationUrl)
  3. After exchanging the code and creating a session, read sk_return_to. Validate and normalize the path. Default to /dashboard or /.

    Express.js
    app.get('/auth/callback', async (req, res) => {
    // ... exchange code ...
    const raw = req.cookies.sk_return_to || '/'
    const safe = raw.startsWith('/') && !raw.startsWith('//') ? raw : '/'
    res.clearCookie('sk_return_to', { path: '/' })
    res.redirect(safe || '/dashboard')
    })
  4. Sign return_to values Optional

    Section titled “Sign return_to values ”

    If you pass return_to via query string or store longer values, compute an HMAC and verify it before redirecting. Reject unsigned or invalid pairs.

    HMAC signing
    import crypto from 'crypto'
    function sign(value, secret) {
    const mac = crypto.createHmac('sha256', secret).update(value).digest('base64url')
    return `${value}|${mac}`
    }
    function verify(signed, secret) {
    const [v, mac] = signed.split('|')
    const good = crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sign(v, secret).split('|')[1]))
    return good ? v : null
    }