如何在React应用中通过OAuth调用Jira REST API
Got it, let's walk through implementing Jira OAuth 2.0 in your React app to fetch a user's task list. Jira recommends OAuth 2.0, and the Authorization Code Flow is the right approach here (since implicit flow is deprecated). Here's a step-by-step guide with practical code examples:
Before writing React code, you need to set up an OAuth app in the Atlassian Developer Console:
- Log into the Atlassian Developer Console and create a new OAuth 2.0 app.
- Fill in basic details, then navigate to the Authorization tab.
- Add your React app's redirect URI (e.g.,
http://localhost:3000/jira-callbackfor local development). - Enable required scopes:
read:jira-work(to access task data) andread:jira-user(to verify the authenticated user). - Save the app and note down your Client ID—you'll need this later.
2.1 Add the Authorization Redirect Button
Create a component that triggers the Jira login flow when clicked:
const JiraAuthButton = () => { const clientId = "YOUR_JIRA_CLIENT_ID"; const redirectUri = "http://localhost:3000/jira-callback"; const scope = "read:jira-work read:jira-user"; // Construct Jira's authorization URL const authUrl = `https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=${clientId}&scope=${encodeURIComponent(scope)}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&prompt=consent`; const handleAuthClick = () => { window.location.href = authUrl; }; return <button onClick={handleAuthClick}>Connect to Jira</button>; }; export default JiraAuthButton;
The prompt=consent parameter ensures the user sees the authorization screen every time (useful for testing—remove it in production).
2.2 Handle the Callback and Fetch an Access Token
Create a callback page component to receive the authorization code and exchange it for an access token:
import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; const JiraCallback = () => { const navigate = useNavigate(); const [error, setError] = useState(null); useEffect(() => { const fetchToken = async () => { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get("code"); const authError = urlParams.get("error"); if (authError) { setError(`Authorization failed: ${authError}`); return; } if (!code) { setError("No authorization code found in the URL"); return; } try { const response = await fetch("https://auth.atlassian.com/oauth/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", client_id: "YOUR_JIRA_CLIENT_ID", code: code, redirect_uri: "http://localhost:3000/jira-callback", }), }); const data = await response.json(); if (response.ok) { // Store tokens (use sessionStorage or a context for better security in production) localStorage.setItem("jiraAccessToken", data.access_token); localStorage.setItem("jiraRefreshToken", data.refresh_token); navigate("/tasks"); // Redirect to your task list page } else { setError(`Failed to get token: ${data.error_description}`); } } catch (err) { setError(`Network error: ${err.message}`); } }; fetchToken(); }, [navigate]); if (error) return <div className="error">Error: {error}</div>; return <div>Authenticating with Jira...</div>; }; export default JiraCallback;
Note for production: Avoid calling the token endpoint directly from the frontend (it exposes your client ID). Instead, proxy this request through your backend server to keep sensitive info secure.
2.3 Fetch User Tasks with the Access Token
Create a component to fetch and display the user's Jira tasks using the access token:
import { useEffect, useState } from "react"; const JiraTasks = () => { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchTasks = async () => { const accessToken = localStorage.getItem("jiraAccessToken"); if (!accessToken) { setError("No access token found—please connect to Jira first."); setLoading(false); return; } // First, get your Jira cloud ID (required for API requests) const resourcesResponse = await fetch("https://api.atlassian.com/oauth/token/accessible-resources", { headers: { Authorization: `Bearer ${accessToken}` }, }); const resources = await resourcesResponse.json(); const cloudId = resources[0].id; try { // Fetch tasks using JQL (adjust the query to your needs) const jql = "assignee = currentUser() AND status != Done"; const tasksResponse = await fetch( `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/search?jql=${encodeURIComponent(jql)}`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: "application/json", }, } ); if (!tasksResponse.ok) throw new Error(`API request failed: ${tasksResponse.statusText}`); const data = await tasksResponse.json(); setTasks(data.issues); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchTasks(); }, []); if (loading) return <div>Loading your Jira tasks...</div>; if (error) return <div className="error">Error: {error}</div>; return ( <div className="task-list"> <h2>Your Jira Tasks</h2> <ul> {tasks.map(task => ( <li key={task.id} className="task-item"> <h3>{task.fields.summary}</h3> <p>Status: <strong>{task.fields.status.name}</strong></p> <p>Priority: {task.fields.priority?.name || "Unassigned"}</p> </li> ))} </ul> </div> ); }; export default JiraTasks;
The JQL query assignee = currentUser() AND status != Done fetches all incomplete tasks assigned to the user—adjust this to match your requirements.
- Token Security: Avoid storing tokens in
localStoragefor sensitive apps—usesessionStorageor a secure context provider. Implement token refresh logic (using the refresh token) to avoid re-authenticating every hour. - CORS: Ensure your React app's domain is added to the allowed origins in your Atlassian app settings.
- Permissions: Double-check that your app has all required scopes for the data you need to access (e.g.,
write:jira-workif you need to update tasks later).
内容的提问来源于stack exchange,提问作者cbutler

