Skip to content

Broken Academy Single Sign On

The application is up and running but when you try to sign in, you’re getting an error. These errors show up in Sentry, which is great, but how can we use Tracing and Logs to help troubleshoot the issue?

Project Issues

Learning Objectives

By the end of this module, you will:

  • Create Custom Spans that will track specific attributes within your application
  • Use Trace Explorer to search for specific spans that match issues in your environment
  • Explore trace waterfalls to understand where in the trace issue happens
  • Implement logs in your application to give you more context around the issues happening within your application
  • Search and review logs to help troubleshoot issues in your application

Frontend Implementation

Let’s start by instrumenting the frontend authentication flow to track login attempts and user credential processing.

  1. Add Sentry Import to Frontend Login Component

    Navigate to the /apps/frontend/src/components/auth/LoginForm.tsx file and add the Sentry import at the top:

    import * as Sentry from '@sentry/react';
  2. Set Up Frontend Logging

    Add the logger destructuring after your imports to access Sentry’s logging functionality:

    const { logger } = Sentry
  3. Replace the Complete handleSSO Function

    Replace the entire ‘handleSSO’ function with this instrumented version:

    const handleSSO = async (provider: string) => {
    setError('');
    setIsLoading(true);
    try {
    await Sentry.startSpan(
    {
    name: 'sso.authentication.frontend',
    op: 'auth.sso',
    attributes: {
    'auth.provider': provider,
    },
    },
    async (span) => {
    const userCredentials = fetchSSOUserCredentials(provider);
    logger.info(logger.fmt`Logging user ${userCredentials.email} in using ${provider}`);
    span.setAttributes({
    'auth.user.id': userCredentials.id,
    'auth.user.email': userCredentials.email,
    'auth.user.name': userCredentials.name,
    'auth.user.avatar': userCredentials.avatar,
    });
    const loginSignature = createAuthenticationToken(userCredentials, provider);
    span.setAttributes({
    'auth.login_signature.defined': loginSignature !== undefined && loginSignature !== null,
    });
    await ssoLogin(provider);
    }
    );
    navigate('/');
    } catch (err: any) {
    logger.error(logger.fmt`Failed to login with ${provider} - issue with loginSignature`);
    setError(`Failed to login with ${provider} - issue with loginSignature`);
    throw err;
    } finally {
    setIsLoading(false);
    }
    };
  4. Test Frontend Tracing

    Try logging in again. Navigate to Explore > Traces in Sentry’s left navigation menu, and search using the span.description filter with sso.authentication.frontend as the value.

    Trace Explorer FE
  5. Examine the Trace

    Clicking into one of the spans will show you the trace waterfall where you can examine the properties of any of he spans submitted.

    Trace Waterfall
  6. Examine the Logs

    Check the Explore > Logs tab for the log entries created by logger.info and logger.error calls.

    Logs

Backend Implementation

Now let’s instrument the backend authentication flow to trace the server-side processing and identify where the issue occurs.

  1. Add Sentry Import to Backend Auth Routes

    Navigate to the /apps/server/modules/auth/routes.ts file and add the Sentry import at the top:

    import * as Sentry from '@sentry/node';
  2. Set Up Backend Logging

    Add the logger destructuring after your imports:

    const { logger } = Sentry
  3. Replace the Complete SSO Route

    Replace the entire authRoutes.post('/sso/:provider' route with this instrumented version:

    authRoutes.post('/sso/:provider', async (req, res) => {
    try {
    const { provider } = req.params;
    const { loginSignature } = req.body;
    await Sentry.startSpan(
    {
    name: 'sso.authentication.server',
    op: 'auth.sso.verify',
    attributes: {
    'auth.provider': provider,
    'auth.login_signature.provided': !!loginSignature,
    'http.method': req.method,
    'http.route': '/sso/:provider',
    },
    },
    async (span) => {
    logger.info(logger.fmt`SSO login attempt with ${provider}`);
    logger.info(logger.fmt`Login signature provided: ${!!loginSignature}`);
    // Add more attributes based on request data
    span.setAttributes({
    'auth.request.body_size': JSON.stringify(req.body).length,
    'auth.request.has_signature': loginSignature !== undefined,
    });
    // TOFIX Module 1: SSO Login with missing login signature
    const signaturePayload = JSON.parse(atob(loginSignature)); // This will throw when loginSignature is undefined
    // Add signature payload details to span
    span.setAttributes({
    'auth.signature.user_id': signaturePayload.sub || null,
    'auth.signature.email': signaturePayload.email || null,
    'auth.signature.name': signaturePayload.name || null,
    'auth.signature.provider': signaturePayload.provider || null,
    'auth.signature.issued_at': signaturePayload.iat || null,
    'auth.signature.expires_at': signaturePayload.exp || null,
    'auth.signature.has_user_data': !!(signaturePayload.userData),
    });
    // Use the rich fake user data from the signature payload, with sensible defaults
    const fakeUserData = signaturePayload.userData || {};
    // Add user data details to span
    span.setAttributes({
    'auth.user.id': fakeUserData.id || null,
    'auth.user.email': fakeUserData.email || null,
    'auth.user.name': fakeUserData.name || null,
    'auth.user.company': fakeUserData.company || null,
    'auth.user.job_title': fakeUserData.jobTitle || null,
    });
    const ssoUser = {
    id: fakeUserData.id || createId(),
    email: fakeUserData.email || `${provider}.user@example.com`,
    name: fakeUserData.name || `${provider.charAt(0).toUpperCase() + provider.slice(1)} User`,
    firstName: fakeUserData.firstName || 'Demo',
    lastName: fakeUserData.lastName || 'User',
    username: fakeUserData.username || 'demo.user',
    avatar: fakeUserData.avatar || 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg',
    company: fakeUserData.company || 'Demo Company',
    jobTitle: fakeUserData.jobTitle || 'Software Developer',
    phone: fakeUserData.phone || '+1-555-0123',
    workEmail: fakeUserData.workEmail || fakeUserData.email,
    role: 'student',
    provider: provider,
    signatureClaims: {
    sub: signaturePayload.sub,
    exp: signaturePayload.exp,
    metadata: {
    permissions: [],
    roles: []
    }
    },
    socialProfile: {
    profileImage: fakeUserData.avatar || 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg',
    verified: true,
    provider: provider
    },
    linkedAccounts: [{
    provider: provider,
    externalId: signaturePayload.sub,
    profile: {
    username: fakeUserData.username || (fakeUserData.email || signaturePayload.email || 'user').split('@')[0],
    avatar: fakeUserData.avatar || 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg'
    }
    }]
    };
    // Add final authentication result to span
    span.setAttributes({
    'auth.result.user_id': ssoUser.id,
    'auth.result.success': true,
    'auth.result.provider_verified': true,
    });
    const responseData = {
    user: ssoUser,
    token: `sso-token-${createId()}`,
    expiresIn: '24h'
    };
    logger.info(logger.fmt`Successful SSO login with ${provider}`);
    res.json(responseData);
    }
    );
    } catch (error: any) {
    Sentry.captureException(error, {
    tags: {
    operation: 'sso.authentication.backend',
    provider: req.params.provider,
    },
    extra: {
    provider: req.params.provider,
    hasLoginSignature: !!req.body.loginSignature,
    requestBody: req.body,
    },
    });
    logger.error(logger.fmt`SSO login error for ${req.params.provider}:`, error);
    throw error;
    }
    });
  4. Test Backend Tracing

    Try logging in again and observe the trace and logs by viewing the Explore > Traces and using the Trace Explorer to search for sso.authentication.server.

    Trace Explorer BE
  5. Examine the Trace

    Clicking into one of the spans will show you the trace waterfall where you can examine the properties of any of he spans submitted.

    Trace Waterfall
  6. Examine the Logs

    Trace Explorer BE
    Logs

You should now see the complete authentication flow traced from frontend to backend, and notice that the loginSignature is being created on the frontend but not being passed properly to the server.

Analyzing the Issue

When we explore these traces and logs, we can see an inconsistency - the loginSignature is being created on the frontend, but not being passed properly to the server.

Fixing the Issue

Fortunately the issue is a trivial one to fix! We determined that our loginSignature wasn’t being passed properly from the frontend to the server, and we can add it to the login request.

  1. Update Frontend to Pass Login Signature

    In the frontend authentication flow, replace:

    await ssoLogin(provider);

    With:

    await ssoLogin(provider, loginSignature);

    This will pass the loginSignature to the server, and we’ll be able to see it in the trace and logs.

Once this is complete, give login a try again and you’ll see the loginSignature being passed to the server and your login will be successful! You’ll be at the landing screen of Sentry Academy.

Course Landing Page