Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
151343 stories
·
33 followers

FunctionGemma: Bringing bespoke function calling to the edge

1 Share
FunctionGemma is a specialized version of our Gemma 3 270M model fine-tuned for function calling.
Read the whole story
alvinashcraft
23 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

What Microsoft’s 2026 Product Plans Reveal About Its AI Push

1 Share

The tech firm reveals a focus on AI agents, Copilot expansion, Microsoft 365 updates, and global infrastructure investment.

The post What Microsoft’s 2026 Product Plans Reveal About Its AI Push appeared first on TechRepublic.

Read the whole story
alvinashcraft
24 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

New Microsoft e-book: 3 reasons point solutions are holding you back

1 Share

While patchwork tools slow defenders down and impact visibility into potential cyberthreats, they’re an unfortunate reality for many organizations. As digital risk accelerates and attack surfaces multiply, security leaders are doing their best to stitch together point solutions while trying to avoid blind spots that cyberattackers can exploit. But point solutions can only go so far. For protection that keeps up with today’s fast-evolving cyberthreats, the way forward is a unified, AI-ready security platform that consolidates telemetry, analytics, and automation across detection, response, exposure management, and cloud security.

In our new e-book, 3 reasons point solutions are holding you back, we share how a unified, AI-ready platform can transform your security operations to help keep your organization safe. Read on to learn more about the key concepts in our new e-book.

What you’ll learn:

  1. The hidden costs of fragmented tools: How disconnected solutions inflate operational costs, slow investigations, and prevent AI from delivering its full potential.
  2. The power of unification: Why a unified platform delivers full-spectrum visibility, predictive defense, and agentic assistance—helping teams respond faster and more effectively.
  3. Real-world results: See how organizations are reducing breach exposure, cutting incident response effort, and lowering costs through consolidation.

Rethinking security for the AI era

AI is transforming cybersecurity for both defenders and threat actors. But disconnected tools prevent defenders from seeing the full picture and block AI from delivering its full value. Without unified data and context, AI models can’t detect subtle patterns or anticipate evolving cyberthreats. Imagine a security approach that doesn’t just react but predicts—one that turns fragmented signals into actionable insight. An AI-ready platform unifies security data into a scalable, intelligent data lake enriched with threat intelligence and mapped into a living security graph. In our e-book, we explore how this shift transforms security from a patchwork of disparate tools to a strategic advantage for organizations—delivering clarity, speed, and resilience in ways point solutions simply can’t match.

The e-book shares more about how AI-ready unity includes the ability to:

  • Predict attack paths and prevent breaches with exposure management.
  • Rapidly remediate with AI-powered protection and improved mean time to resolution (MTTR).
  • Detect emerging cyberthreats using cyberattacker-level intelligence.
  • Continuously optimize security operations center (SOC) operations with centralized data and advanced analytics.

Measurable benefits of a unified security platform

By moving away from fragmented portfolios, organizations see dramatic improvements in efficiency and resilience. Instead of drowning in alert triage, security teams can redirect their focus to proactive remediation and prevention. And AI-powered detection shortens containment from hours to minutes—often halting ransomware before encryption begins.

A chart showing that a unified security strategy leads to better and more responsive protection.
Figure 1. A graphic showing three measurable impacts of Microsoft Defender.

Stay ahead of accelerating cyberthreats

Microsoft Defender, powered by Microsoft Sentinel, unifies prevention, detection, and response across ransomware, phishing, malware, and other advanced cyberthreats. Together with Microsoft Security Copilot, the stack brings AI-powered guidance and autonomous protection to investigations and response.

The e-book shares more about the key benefits, including:

  • Unified foundation: Security information and event management (SIEM), data lake, and graph in one platform.
  • Proactive resilience: Continuous exposure management and prioritized prevention.
  • AI-accelerated defense: Generative guidance and autonomous response.
  • Operational efficiency: Simplified onboarding, connectors, and workflows.
  • Strategic value: Lower costs through consolidation and higher return on investment.

Ready to move beyond point solutions?

Download the 3 reasons point solutions are holding you back e-book and discover how a unified, AI-ready platform can help your team stay ahead of cyberthreats and prepare for the future.

Envision a future where defenders and AI agents work together. Hear Charlie Bell, Executive Vice President of Microsoft Security, and Vasu Jakkal, Corporate Vice President of Microsoft Security Business, share how leading organizations are securing AI innovation at scale—plus get demos and actionable steps. Watch now!

Learn more

To learn more about Microsoft Security solutions, visit our website. Bookmark the Security blog to keep up with our expert coverage on security matters. Also, follow us on LinkedIn (Microsoft Security) and X (@MSFTSecurity) for the latest news and updates on cybersecurity. 

The post New Microsoft e-book: 3 reasons point solutions are holding you back appeared first on Microsoft Security Blog.

Read the whole story
alvinashcraft
24 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Bring Your Own Key (BYOK) Is Now Live in JetBrains IDEs

1 Share

Bring Your Own Key (BYOK) is now available in the AI chat inside JetBrains IDEs as well as for AI agents, including JetBrains’ Junie and Claude Agent. Whether you’re looking to use cutting-edge frontier models, cost-efficient small models, locally hosted private models, or experimental research previews, BYOK makes it easy to bring them all directly into your JetBrains IDE – with no JetBrains AI subscription required.

Freedom to choose your provider and models

The feature is now officially live, allowing you to connect your own API keys from Anthropic, OpenAI, and other OpenAI API-compatible providers. You will also be able to use both Junie and Claude Agent, as long as your chosen provider offers the models the agents require. 

No vendor lock-in means you’re now free to choose your preferred AI provider or model. You get full transparency over costs on your selected platform – with no hidden quotas or unexpected interruptions – and enhanced privacy and security. Your API keys are stored and managed locally on your machine and never shared with JetBrains.

Getting started with BYOK

Getting started is simple: Install the JetBrains AI Assistant plugin in your IDE and select Bring Your Own API Key on the AI chat start screen. Choose a provider, enter your API key, and the available models will automatically appear in your model picker, ready to use in the chat.

If you’re already using AI Assistant and have it installed, whether on a paid plan or the free tier, you can configure BYOK at any time via Settings | Tools | AI Assistant | Models.

Get the best experience by pairing BYOK with JetBrains AI

Please note that not all AI features are guaranteed to work with third-party provider models. For cloud code completion and other editor-integrated AI features, we recommend activating JetBrains AI (no paid subscription required). To enable your JetBrains AI subscription, go to Settings | Tools | AI Assistant | Models and click Activate JetBrains AI in the JetBrains AI section.

This setup enables your own key and JetBrains AI to work together, giving you full feature coverage and the best possible experience. Your key will be prioritized wherever possible, and the built-in model picker allows you to select your desired provider right from the AI chat.

What’s next for BYOK

This is just the beginning! We’re continuing to add more providers, including Google Gemini, Azure, and Amazon Bedrock, while also refining BYOK to make it more convenient and user-friendly.

As always, we’d love to hear your thoughts, feedback, and any feature requests you’d like us to prioritize!

Read the whole story
alvinashcraft
26 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

Best Programming Courses in 2025: New and Favorite Picks on JetBrains Academy

1 Share

“It’s tiiiiiiiiiiiime!”, and not just for Mariah Carey on the playlist. Here’s our annual roundup of JetBrains Academy courses – the favorites you loved this year and the new courses launched in 2025.

If you’re trying to figure out where to start, what to learn next, or how to invest in your future skills, this overview will help you navigate the best options and find a path that fits your goals.

Top in-IDE courses

Our in-IDE courses are built directly inside the tools developers use every day, so you learn by doing – writing real code, solving real tasks, and building skills that are sought after by employers. All in-IDE courses are free of charge, and as a student, you can apply for the JetBrains Student Pack and get a free licence for JetBrains IDEs. 

Let’s take a look at the courses learners loved most this year.

❤️ 100 Days of Code – The Complete Python Pro Bootcamp

Best for: motivated learners who want to build projects every day.

Angela Yu’s bestselling course comes to life inside PyCharm. You’ll build 100 real projects in 100 days, from data dashboards and simple games to full web apps, APIs, and automation tools. You’ll be learning Python, the most popular programming language, and a great place to  start your developer career. Join over a million learners who’ve completed the course and created a beautiful portfolio of 100 Python projects ready to show to future employers.

❤️ Introduction to Python

Best for: absolute beginners who want to get started.

With Python being the most popular language among developers in 2025, it’s no surprise that another Python course has been a hit among learners. This fan-favorite helps complete beginners write real code from day one. You’ll learn essentials like variables, loops, functions, and data structures while using the same professional developer tools you’d use on the job. After finishing, you’ll be ready to continue learning, and our structured Python learning roadmap can help you choose what to explore next.

❤️ AtomicKotlin

Best for: beginners to intermediate Kotlin learners.

Designed for both beginners and experienced programmers, this course contains exercises that accompany the Atomic Kotlin book. Start from scratch and progress atom by atom – write real code, test your understanding, and learn with instant IDE feedback and guided hints. 

❤️ 100 Exercises to Learn Rust

Best for: learners who want a hands-on path into Rust.

Rust is rapidly growing in popularity. The 2025 JetBrains Developer Ecosystem Survey results show that it’s among the top languages developers plan to adopt next, with 10% of respondents saying they want to learn it. It’s no surprise then that this course, which is based on Luca Palmieri’s 100 Exercises to Learn Rust, was so popular this year. Each lesson begins with a failing test, and your task is to write the code that makes it pass. By the end, you’ll have completed 100 carefully selected challenges and gained confidence writing clean, idiomatic Rust in a real development environment.

❤️ Introduction to JavaScript Programming

Best for: beginners interested in web development.

JavaScript isn’t just popular – it’s at the center of what most developers actually build. Three out of four developers work on websites or business software, where JavaScript is the industry standard. 

This course is a perfect starting point for learning JavaScript from scratch. You’ll pick up all of the core concepts needed to understand how modern websites work and start writing interactive code inside a real IDE. By the end, you’ll have the foundation to move confidently into frontend or full-stack development.  If you want to keep going, our Full-Stack JavaScript course is the ideal next step.

New courses in 2025

This year, we focused on giving learners even more ways to explore programming. Alongside new IDE-based courses, we launched partnerships, expanded to new platforms, and introduced completely new skill paths. Here’s a look at what arrived in 2025 and what’s coming next.

Skill Paths from JetBrains Academy and AWS

In October, we released our first joint Skill Paths with AWS – free to complete using the AWS Free Tier. These skill paths give you practical, real-world experience. You’ll write Python microservices, containerize them with Docker, deploy on AWS ECS or EC2, and even build and launch a full chat app using React and Flask.

Learners have already earned more than 40 certificates co-branded by JetBrains Academy and AWS. Get yours by completing the course – a strong addition to your LinkedIn profile or portfolio.

Early next year, we’ll also introduce new AI and LLM Skill Paths that dive into machine learning, reinforcement learning, and building applications with LLMs. You’ll learn how to train models with SageMaker, create Bedrock-powered assistants, and work with LangChain in real projects. At the end, you’ll have a working app or LLM endpoint, along with a verified certificate from JetBrains Academy and AWS. Save your spot on the waiting list!

AI-Assisted Programming With JetBrains Academy and Nebius

Over 65% of developers anticipate that AI proficiency will become a job requirement. AI tools are reshaping how we write and test code, but using them effectively isn’t always obvious. That’s why we teamed up with Nebius, an AI cloud platform, to create a new course series that helps developers move beyond the hype and learn how to work with AI in practical, meaningful ways. 

You’ll write and debug Python code with AI support, use AI for QA and bug detection, automate routine steps with AI agents, explore common AI development tools, and build and test projects with Junie, the AI coding agent by JetBrains, directly inside the IDE.

If you’re wondering whether learning to code still matters in an AI-driven world, we’ve covered this in our recent blog series, from why it’s still worth learning to code and exploring the psychology of beginner programmers to whether you should use AI at all.

JetBrains Academy Courses on Coursera

This year, JetBrains Academy expanded to Coursera, bringing our IDE-based learning experience to one of the world’s largest education platforms. Two courses already support full IDE integration, so you can complete all coding tasks directly in PyCharm – Python from Scratch: Learn by Coding and AI-Assisted Programming

This gives you a full IDE setup while you study – the same tools developers use, tightly connected to your Coursera coursework. In 2026, we’ll add even more JetBrains Academy courses to Coursera. Stay tuned for more!

Java Foundations Professional Certificate

Java may not be the newest language, but it remains one of the most in-demand. If you’re considering a career as a developer, Java is a solid place to begin.

That’s why JetBrains has partnered with LinkedIn to offer the Java Foundations Professional Certificate, exclusively on LinkedIn Learning. By completing this learning path, you’ll enhance your LinkedIn profile, validate your Java skills, and get real-world experience. You’ll work in IntelliJ IDEA, the industry’s leading IDE for Java development, gaining practical knowledge essential for your career. By the end of the series, you’ll have acquired the skills to apply for junior developer positions directly on LinkedIn.

Thanks for joining us on this tour of the courses that shaped 2025! If you’d like to explore even more learning courses, you can always browse the full JetBrains Academy catalog, with more than 120 courses designed to help strengthen your CV, build a standout portfolio, and ace IT interviews. 

Happy learning!

The JetBrains Academy team

Read the whole story
alvinashcraft
26 minutes ago
reply
Pennsylvania, USA
Share this story
Delete

JWT Authentication in React: Secure Routes, Context, and Token Handling

1 Share

JWT Authentication in React: Secure Routes, Context, and Token Handling

TL;DR: Learn how to secure your React app with JWT authentication. Start by setting up three route types: public for everyone, protected for logged-in users, and restricted for login or signup only. Then, create an AuthContext to manage login, logout, and token refresh across the app. Finally, add API services—public for authentication actions and authenticated ones that attach tokens and handle expiry. With this setup, your app will be secure, scalable, and deliver a smooth experience for users.

Modern web apps rely on decoupled frontends and backends. They often communicate via stateless REST (Representational State Transfer) APIs. Since the server doesn’t track user identity by default, sensitive data can be exposed without proper authentication. That’s why adding a secure method, such as JWT, is essential to protect your app and its users.

JWT provides a lightweight, stateless method for verifying user identity across requests. Implementing it correctly in your React apps ensures:

  • Secure access control for routes and APIs.
  • Scalable architecture without session overhead.
  • Better user experience with token refresh and auto-logout.

Syncfusion React UI components are the developers’ choice to build user-friendly web applications. You deserve them too.

JWT: An overview

JWT (JSON Web Tokens) is a popular tokenization method in which the server returns a signed token to the client upon successful user login. This token is then stored on the client-side and is sent in every subsequent request to the server. It establishes the authenticity of the request and then returns the response accordingly.

Once the token has expired, a refreshed token is returned, and the flow continues. Upon logout, we discard the token to prevent it from being misused.

The token is signed with the user’s details and other information, depending on its configuration.

Important note: As the JWT token is stored on the client-side, it is accessible to the end user. We should not store sensitive information in the token, as it is only signed and encoded using Base64URL and not encrypted.

A JWT token is composed of three different parts separated by a dot:

  • Header
  • Payload
  • Signature

Refer to the following example.

const token =

"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";

const [headerEncoded, payloadEncoded, signature] = token.split('.');

Implementing JWT authentication in a React app

The following image illustrates the working principle of JWT authentication.

JWT Authentication workflow

To implement the above logic, we need to create the following routes:

  • Public routes: Open to everyone, no login required. Perfect for pages like About Us or Contact.
  • Protected routes: Accessible only after authentication. If you’re not logged in, you’ll be redirected to the login page. Great for dashboards or user profiles.
  • Restricted routes: Designed for unauthenticated users. If you’re already logged in, you’ll be sent to the dashboard instead. Ideal for login or signup pages.

Then, we’ll take things a step further by centralizing all authentication logic. To do this, we’ll create an AuthContext and wrap our routes inside it. This ensures that the authentication state and related actions are easily accessible anywhere in the app.

Next, we’ll set up API services:

  • Public APIs: Handle authentication actions that don’t need a token.
  • Restricted APIs: Automatically attach the token to every request. They’ll also intercept responses to check for token-related errors, such as an expired token, and log the user out if necessary.

With this flow in place, we’re ready to start coding our React app and bring authentication to life.

Step 1: Install the dependencies

First, set up the project and install the required packages:

npx create-react-app react-jwt-auth
cd react-jwt-auth
npm install axios jwt-decode react-router-dom

Here’s what each dependency does:

  • axios:  A powerful HTTP client that supports middlewares and request interceptors, making API calls easier to manage.
  • jwt-decode: A handy library to decode any well-formed JWT token.
  • react-router-dom: Enables smooth client-side navigation in your React app.

Next, create the directory structure and add the necessary files to organize your authentication flow.

Building secure routes with JWT in React

Step 2: Create the AuthContext

Next, we’ll create an AuthContext, which will act as the central hub for all authentication logic. It will:

  • Handle login and logout actions to easily manage user sessions.
  • Maintain user state and loading state, allowing you to check if the user is logged in or if the app is still fetching data.
  • Provide these states and actions to any component wrapped inside the context, making authentication accessible throughout the app.

Look at the example code below.

//contexts/AuthContext.jsx
import React, { createContext, useContext, useState, useEffect } from "react";
import authService from "../services/authService";

const AuthContext = createContext();

export const useAuth = () => {
    const context = useContext(AuthContext);
    if (!context) {
        throw new Error("useAuth must be used within an AuthProvider");
    }
    return context;
};

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const initializeAuth = async () => {
            try {
                // Check if we have a valid session by calling the server
                const userData = await authService.checkAuthStatus();

                if (userData) {
                    setUser(userData);
                }
            } catch (error) {
                console.error("Auth initialization error:", error);
                // Don't logout here as cookies might still be valid
            } finally {
                setLoading(false);
            }
        };

        initializeAuth();
    }, []);

    const login = async (username, password) => {
        try {
            const userData = await authService.login(username, password);
            setUser(userData);
            return userData;
        } catch (error) {
            throw error;
        }
    };

    const logout = async () => {
        try {
            await authService.logout();
        } catch (error) {
            console.error("Logout error:", error);
        } finally {
            setUser(null);
        }
    };

    // Refresh user data from current token
    const refreshUser = () => {
        const userData = authService.getCurrentUser();
        setUser(userData);
        return userData;
    };

    const value = {
        user,
        login,
        logout,
        refreshUser,
        loading,
        isAuthenticated: !!user && !!authService.getAccessToken(),
    };

    return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

All Syncfusion’s 145+ React UI components are well-documented. Refer to them to get started quickly.

Step 3: Creating API services

Here, we’ll create two separate services:

  • Public APIs
  • Authenticated APIs

Public APIs

  • These work without token access and handle actions such as sign-up, login, and logout.
  • After login, the token is stored in memory under the context for persistence.
  • If the user refreshes the page, we retrieve the token from the cookie and store it in memory again. (We use cookies instead of local storage to avoid XSS attacks.)
  • User details are fetched by decoding the JWT token and stored in memory.
  • Access tokens stored in memory are cleared or refreshed on page reload or close.
//services/authService.js
import axios from "axios";
import jwtDecode from "jwt-decode";

const API_URL = process.env.REACT_APP_API_URL || "http://localhost:8080/api/auth/";

// Mock API calls (replace with real endpoints)
const api = {
    // JWT Login
    login: async (email, password) => {
        // Simulate API call
        await new Promise((resolve) => setTimeout(resolve, 1000));
        if (email && password) {
            return {
                data: {
                    id: 1,
                    email,
                    username: "John Doe",
                    accessToken: "mock_jwt_access_token_" + Date.now(),
                    refreshToken: "mock_jwt_refresh_token_" + Date.now(),
                },
            };
        }
        throw new Error("Invalid credentials");
    },
};

class AuthService {
    constructor() {
        this.isRefreshing = false;
        this.failedQueue = [];
        this.accessToken = null; // Store in memory only
    }

    // Configure axios to include cookies in requests
    configureAxios() {
        axios.defaults.withCredentials = true;
    }

    async login(username, password) {
        try {
            const response = await axios.post(
                API_URL + "signin",
                { username, password },
                { withCredentials: true } // Include cookies in request
            );

            // Access token should be returned in response body for memory storage
            // Refresh token should be set as httpOnly cookie by server
            if (response.data.accessToken) {
                this.accessToken = response.data.accessToken;
                return this.decodeToken(response.data.accessToken);
            }

            throw new Error("No access token received");
        } catch (error) {
            throw error;
        }
    }

    logout() {
        // Clear memory token
        this.accessToken = null;
        this.isRefreshing = false;
        this.failedQueue = [];

        // Call logout endpoint to clear httpOnly cookie
        return axios
            .post(API_URL + "logout", {}, { withCredentials: true })
            .catch((error) => {
                console.error("Logout error:", error);
            });
    }

    register(username, email, password) {
        return axios.post(
            API_URL + "signup",
            { username, email, password, },
            { withCredentials: true }
        );
    }

    // Decode JWT to get user information
    decodeToken(token = null) {
        const tokenToUse = token || this.accessToken;
        if (!tokenToUse) {
            return null;
        }

        try {
            const decoded = jwtDecode(tokenToUse);

            // Check if token is expired
            const currentTime = Date.now() / 1000;
            if (decoded.exp < currentTime) {
                this.accessToken = null;
                return null;
            }

            return {
                id: decoded.sub || decoded.id,
                username: decoded.username,
                email: decoded.email,
                roles: decoded.roles || [],
                exp: decoded.exp,
                iat: decoded.iat,
            };
        } catch (error) {
            console.error("Token decode error:", error);
            this.accessToken = null;
            return null;
        }
    }

    getCurrentUser() {
        return this.decodeToken();
    }

    getAccessToken() {
        return this.accessToken;
    }

    // Check if token is expired or will expire soon (within 5 minutes)
    isTokenExpired(token = null) {
        const tokenToUse = token || this.accessToken;
        if (!tokenToUse) return true;
        try {
            const decoded = jwtDecode(tokenToUse);
            const currentTime = Date.now() / 1000;
            const bufferTime = 5 * 60; // 5 minutes buffer
            return decoded.exp < currentTime + bufferTime;
        } catch (error) {
            return true;
        }
    }

    async refreshToken() {
        try {
            // Refresh token is sent automatically as httpOnly cookie
            const response = await axios.post( API_URL + "refresh", {}, { withCredentials: true, });
            if (response.data.accessToken) {
                this.accessToken = response.data.accessToken;
                return response.data.accessToken;
            }

            throw new Error("No access token in refresh response");
        } catch (error) {
            // Refresh failed, clear memory token
            this.accessToken = null;
            throw error;
        }
    }

    // Check authentication status by calling a protected endpoint
    async checkAuthStatus() {
        try {
            const response = await axios.get(API_URL + "me", {
                withCredentials: true,
                headers: this.accessToken ? { Authorization: `Bearer ${this.accessToken}`, } : {},
            });

            if (response.data.accessToken) {
                this.accessToken = response.data.accessToken;
            }

            return this.decodeToken();
        } catch (error) {
            this.accessToken = null;
            return null;
        }
    }

    // Process queued requests after token refresh
    processQueue(error, token = null) {
        this.failedQueue.forEach(({ resolve, reject }) => {
            if (error) {
                reject(error);
            } else {
                resolve(token);
            }
        });

        this.failedQueue = [];
    }

    // Add request to queue during token refresh
    addToQueue(resolve, reject) {
        this.failedQueue.push({ resolve, reject });
    }
}

// Create singleton instance
const authService = new AuthService();

// Configure axios globally
authService.configureAxios();

export default authService;

Authenticated APIs

This service uses request and response interceptors:

  • Request interceptor: Automatically attaches the token to every API call.
  • Response interceptor: Checks if the token has expired. If so, it logs out the user and forces re-authentication.
//services/apiService.js
import axios from "axios";
import authService from "./authService";

const API_BASE_URL =
    process.env.REACT_APP_API_URL || "http://localhost:8080/api/";

// Create axios instance with secure defaults
const apiService = axios.create({
    baseURL: API_BASE_URL,
    timeout: 10000,
    withCredentials: true, // Always include cookies
    headers: {
        "Content-Type": "application/json",
    },
});

// Request interceptor to add auth token from memory
apiService.interceptors.request.use(
    (config) => {
        const token = authService.getAccessToken();
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
    },
    (error) => {
        return Promise.reject(error);
    }
);

// Response interceptor with secure refresh token handling
apiService.interceptors.response.use(
    (response) => {
        return response;
    },
    async (error) => {
        const originalRequest = error.config;

        // Handle 401 errors with token refresh
        if (error.response?.status === 401 && !originalRequest._retry) {
            if (authService.isRefreshing) {
                // Queue request if refresh is in progress
                return new Promise((resolve, reject) => {
                    authService.addToQueue(resolve, reject);
                })
                    .then((token) => {
                        originalRequest.headers.Authorization = `Bearer ${token}`;
                        return apiService(originalRequest);
                    })
                    .catch((err) => {
                        return Promise.reject(err);
                    });
            }

            originalRequest._retry = true;
            authService.isRefreshing = true;

            try {
                const newToken = await authService.refreshToken();

                // Process queued requests
                authService.processQueue(null, newToken);

                // Retry original request
                originalRequest.headers.Authorization = `Bearer ${newToken}`;
                return apiService(originalRequest);
            } catch (refreshError) {
                // Refresh failed, process queue with error
                authService.processQueue(refreshError, null);

                // Clear tokens and redirect to login
                await authService.logout();
                window.location.href = "/login";
                return Promise.reject(refreshError);
            } finally {
                authService.isRefreshing = false;
            }
        }

        return Promise.reject(error);
    }
);

export default apiService;

Step 4: Configuring the routes

Now, let’s create Protected and Authenticated route wrappers. These will redirect users to the correct page based on whether they are authenticated (i.e., the JWT token is valid). 

Protected route

  • It requires authentication.
  • If the user is not logged in, they are redirected to the login page.
//components/ProtectedRoute.jsx
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";

const ProtectedRoute = ({ children }) => {
    const { user, loading } = useAuth();
    const location = useLocation();

    if (loading) {
        return <div>Loading...</div>;
    }

    if (!user) {
        // Redirect to login page with return url
        return <Navigate to="/login" state={{ from: location }} replace />;
    }

    return children;
};

export default ProtectedRoute;

Authenticated route

  • It cannot be accessed if the user is already logged in.
  • If the user tries to access the login page while logged in, they are redirected to the dashboard page.
//components/AuthenticatedRoute.jsx
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";

const AuthenticatedRoute = ({ children }) => {
    const { user, loading } = useAuth();
    const location = useLocation();

    if (loading) {
        return <div>Loading...</div>;
    }

    if (user) {
        // Redirect to dashboard page with return url
        return <Navigate to="/dashboard" state={{ from: location }} replace />;
    }

    return children;
};

export default AuthenticatedRoute;

Adding the routes to the application

Now, configure the entry index file by defining the routes and wrapping them inside AuthContext.

In the following example, the respective protected and restricted routes are wrapped inside the component.

import {
    BrowserRouter as Router,
    Routes,
    Route,
    Navigate,
} from "react-router-dom";
import { AuthProvider } from "./contexts/AuthContext";
import ProtectedRoute from "./components/ProtectedRoute";
import AuthenticatedRoute from "./components/AuthenticatedRoute";
import Login from "./components/Login";
import Dashboard from "./components/Dashboard";
import About from "./components/About";
import "./styles.css";

function App() {
    return (
        <AuthProvider>
            <Router>
                <div className="App">
                    <Routes>
                        <Route
                            path="/login"
                            element={
                                <AuthenticatedRoute>
                                    <Login />
                                </AuthenticatedRoute>
                            }
                        />
                        <Route path="/about-us" element={<About />} />
                        <Route
                            path="/dashboard"
                            element={
                                <ProtectedRoute>
                                    <Dashboard />
                                </ProtectedRoute>
                            }
                        />
                        <Route path="/" element={<Navigate to="/dashboard" replace />} />
                        <Route path="*" element={<h1>404</h1>} />
                    </Routes>
                </div>
            </Router>
        </AuthProvider>
    );
}

export default App;

In the above code example,

  • /login: It is an authenticated route that cannot be accessed if the user is already logged in.
  • /dashboard: It is a protected route that cannot be accessed if the user is not logged in.
  • /about-us: It is a public route that can be accessed without any restriction.
  • Any other route apart from these will throw a 404 error.

Be amazed exploring what kind of application you can develop using Syncfusion React components.

Step 5: Designing the pages

We’re now building three key pages that shape the core experience:

  • Login
  • Dashboard
  • About us

Login

The login page uses the login method that is exposed from the AuthContext. Once the login is successful (mocked with a non-empty Email and Password), it generates a dummy auth token and stores it in the storage. It will then be passed to other subsequent requests.

// components/Login.jsx
import React, { useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { useAuth } from "../contexts/AuthContext";

const Login = () => {
    const [formData, setFormData] = useState({
        email: "",
        password: "",
    });
    const [error, setError] = useState("");
    const [loading, setLoading] = useState(false);

    const { login } = useAuth();
    const navigate = useNavigate();
    const location = useLocation();

    const from = location.state?.from?.pathname || "/dashboard";

    const handleChange = (e) => {
        setFormData({
            ...formData,
            [e.target.name]: e.target.value,
        });
    };

    const handleSubmit = async (e) => {
        e.preventDefault();
        setError("");
        setLoading(true);

        try {
            await login(formData.email, formData.password);
            navigate(from, { replace: true });
        } catch (error) {
            console.log(error);
            setError(error.response?.data?.message || "Login failed");
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="login-container">
            <form onSubmit={handleSubmit} className="login-form">
                <h2>Login</h2>

                {error && <div className="error-message">{error}</div>}

                <div className="form-group">
                    <label htmlFor="email">Email:</label>
                    <input
                        type="email"
                        id="email"
                        autoComplete={"off"}
                        name="email"
                        value={formData.email}
                        onChange={handleChange}
                        required
                    />
                </div>

                <div className="form-group">
                    <label htmlFor="password">Password:</label>
                    <input
                        type="password"
                        id="password"
                        name="password"
                        value={formData.password}
                        onChange={handleChange}
                        required
                    />
                </div>

                <button type="submit" disabled={loading}>
                    {loading ? "Logging in..." : "Login"}
                </button>
            </form>
        </div>
    );
};

export default Login;

After executing the above code example, our login page will be created like in the following image.Implementing login and routing to dashboard

Dashboard

The dashboard is only visible when the user is logged in or has a valid access token. When the page loads, it automatically makes a network request to fetch dashboard data, sending the Bearer JWT token in the Authorization header to ensure secure access.

// components/Dashboard.jsx
import React, { useState, useEffect } from "react";
import { useAuth } from "../contexts/AuthContext";
import { useTokenMonitor } from "../hooks/useTokenMonitor";
import apiService from "../services/apiService";

const Dashboard = () => {
    const { user, logout } = useAuth();
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState("");

    // Monitor token expiration
    useTokenMonitor();

    useEffect(() => {
        fetchProtectedData();
    }, []);

    const fetchProtectedData = async () => {
        try {
            setLoading(true);
            const response = await apiService.get("/protected-endpoint");
            setData(response.data);
            setError("");
              } catch (error) {
            console.error("Failed to fetch data:", error);
            setError("Failed to fetch data");
        } finally {
            setLoading(false);
        }
    };

    const handleLogout = async () => {
        await logout();
    };

    if (loading) return <div>Loading...</div>;

    return (
        <div className="dashboard">
            <header className="dashboard-header">
                <h1>Dashboard</h1>
                <div className="user-info">
                    <span>Welcome, {user?.username}!</span>
                    <span className="user-email">({user?.email})</span>
                    <button onClick={fetchProtectedData}>Refresh Data</button>
                    <button onClick={handleLogout}>Logout</button>
                </div>
            </header>

            <main className="dashboard-content">
                {error && <div className="error-message">{error}</div>}

                <div className="user-details">
                    <h3>User Information (from JWT)</h3>
                    <pre>{JSON.stringify(user, null, 2)}</pre>
                </div>

                <div className="data-section">
                    <h2>Protected Data</h2>
                    {data ? (
                        <pre>{JSON.stringify(data, null, 2)}</pre>
                    ) : (
                        <p>No data available</p>
                    )}
                </div>
            </main>
        </div>
    );
};

export default Dashboard;

Here’s what the output looks like.

JWT-Protected dashboard for Auth-Only access

Here, the API request fails as we are making a genuine request.

Handling API request failure in secure flow

Monitor token

Next, we’ll create a custom hook to monitor the token used in the Dashboard component. This hook checks whether the token has expired and, if needed, requests a refresh to maintain authentication.

If the refresh attempt fails, the user is logged out and prompted to sign in again for security.

// src/hooks/useTokenMonitor.js
import { useEffect, useCallback } from "react";
import { useAuth } from "../contexts/AuthContext";
import authService from "../services/authService";

export const useTokenMonitor = () => {
    const { refreshUser, logout } = useAuth();

    const checkTokenExpiration = useCallback(async () => {
        const token = authService.getAccessToken();

        if (!token) {
            return;
        }

        // Check if token is expired or will expire soon
        if (authService.isTokenExpired(token)) {
            try {
                await authService.refreshToken();
                // Update user data from new token
                refreshUser();
            } catch (error) {
                console.error("Token refresh failed:", error);
                logout();
            }
        }
    }, [refreshUser, logout]);

    useEffect(() => {
        // Check token expiration every 5 minutes
        const interval = setInterval(checkTokenExpiration, 5 * 60 * 1000);

        // Check immediately on mount
        checkTokenExpiration();

        // Listen for focus events to check token when user returns to tab
        const handleFocus = () => {
            checkTokenExpiration();
        };

        window.addEventListener("focus", handleFocus);

        return () => {
            clearInterval(interval);
            window.removeEventListener("focus", handleFocus);
        };
    }, [checkTokenExpiration]);

    return { checkTokenExpiration };
};

About-us

Finally, let’s design the About-us page with a header tag that introduces your About section.

// components/About.js
const About = () => {
    return <h1>About us</h1>;
};

export default About;

Refer to the following image.

Building a basic About Us component in React

Style.css

Here’s the code example for the styles used in the entire application.

.App {
    text-align: center;
}

.login-container {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #f5f5f5;
}

.login-form {
    background: white;
    padding: 2rem;
   border-radius: 8px;
   box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
   width: 100%;
   max-width: 400px;
}

.form-group {
    margin-bottom: 1rem;
    text-align: left;
}

.form-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: bold;
}

.form-group input {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 1rem;
}

button {
    width: 100%;
    padding: 0.75rem;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    font-size: 1rem;
    cursor: pointer;
}

button:hover {
    background-color: #0056b3;
}

button:disabled {
    background-color: #ccc;
    cursor: not-allowed;
}

.error-message {
  background-color: #f8d7da;
  color: #721c24;
  padding: 0.75rem;
  border-radius: 4px;
  margin-bottom: 1rem;
}

.dashboard {
    min-height: 100vh;
    background-color: #f8f9fa;
}

.dashboard-header {
    background: white;
    padding: 1rem 2rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.user-info {
    display: flex;
    align-items: center;
    gap: 1rem;
}

.dashboard-content {
    padding: 2rem;
}

.data-section {
    background: white;
    padding: 2rem;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    text-align: left;
}

GitHub reference

Explore the endless possibilities with Syncfusion’s outstanding React UI components.

Conclusion

Thanks for reading! Implementing JWT authentication can seem tricky, and mistakes often lead to security gaps. By following best practices like proper token expiry and refresh mechanisms, you can keep your app secure and user sessions smooth.

Ready to take this further? Start integrating these steps into your project today and share your experience with us!

Got questions or ideas? Drop them in the comments or reach out via our support forum, support portal, and feedback portal. We’d love to hear from you!

Read the whole story
alvinashcraft
26 minutes ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories