You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Access Token调用API时出现401未授权(.NET WebApi/React.js模板项目)

问题:使用IdentityServer令牌调用API端点持续返回401 Unauthorized

我通过Visual Studio模板创建了包含WebAPI(带独立账户认证/授权)和React.js的项目,注册、登录功能正常,但调用authService.getAccessToken()获取令牌后访问/user端点时,持续收到401 Unauthorized响应,偶尔重启项目并重新登录后可正常访问。

我扩展了IdentityUser模型添加了StravaAthleteId属性,并创建了/user控制器。该端点在使用有效令牌时可正常工作,但当前组件中,尽管authService.getAccessToken()返回了令牌,请求仍被拒绝,浏览器开发者工具显示响应头包含Www-Authenticate: Bearer error="invalid_token"。组件始终显示“You need to connect your Strava account”,但数据库中对应的WebAppUser已存在StravaAthleteId且我已完成认证,未修改过默认的授权端点或工具。


前端主组件代码

import { Link, NavLink } from "react-router-dom";
import authService from "../api-authorization/AuthorizeService";
import { WebAppUser } from "./models/webAppUser";
import { ApplicationPaths } from "../api-authorization/ApiAuthorizationConstants";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";

function SegmentSniper() {
  const [userName, setUsername] = useState(null);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const loginPath = `${ApplicationPaths.Login}`;
  const [appUser, setAppUser] = useState<WebAppUser>();

  useEffect(() => {
    const _subscription = authService.subscribe(() => populateState());
    populateState();

    return () => {
      authService.unsubscribe(_subscription);
    };
  });

  async function populateState() {
    const [isAuthenticated, user] = await Promise.all([
      authService.isAuthenticated(),
      authService.getUser(),
    ]);
    setIsAuthenticated(isAuthenticated);
    setUsername(user && user.name);
    console.log(`user from populate state = ${JSON.stringify(user, null, 4)}`);
    const authToken = await authService
      .getAccessToken()
      .then((res) => console.log(`user token: ${res}`));
    try {
      const response = await fetch("/user", {
        headers: { Authorization: `Bearer ${authToken}` },
      });

      if (!response.ok) {
        console.log(`There was an error fetching the user data.`);
      } else {
        response
          .json()
          .then((resolvedData) => setAppUser(resolvedData))
          .catch((error) =>
            console.log(`There was an error fetching the user data. ${error}`)
          );
      }
    } catch (error) {
      console.log(`There was an error fetching the data. ${error}`);
    }
  }

  return !isAuthenticated ? (
    <>
      <main className="container">
        <div className="row justify-content-center mt-3 mb-3">
          <div className="col-6">
            <p>You must be logged in to access this app.</p>
            <p>
              Click
              <Link to={loginPath}> here </Link>
              to login.
            </p>
          </div>
        </div>
      </main>
    </>
  ) : isAuthenticated && !appUser?.stravaAthleteId ? (
    <>
      <main className="container">
        <div className="row justify-content-center mt-3 mb-3">
          <div className="col-6">
            <p>You need to connect your Strava Account.</p>
            <p>
              Click
              <Link to={loginPath}>
                {" "}
                <img src=".\assets\stravaImages\btn_strava_connectwith_orange\btn_strava_connectwith_orange.png" />{" "}
              </Link>
              to get started.
            </p>
          </div>
        </div>
      </main>
    </>
  ) : (
    <>
      <main className="container">
        <div className="row justify-content-center mt-3 mb-3">
          <div className="main-tile">
            <ul>
              <li style={{ cursor: "pointer" }}>
                <Link to="./athlete">View Athlete Details</Link>
              </li>
              <li style={{ cursor: "pointer" }}>
                <Link to="./activities">Segment Sniper</Link>
              </li>
              <li style={{ cursor: "pointer" }}>
                <Link to="./token-maintenance">Token Maintenance</Link>
              </li>
            </ul>
          </div>
          <div>
            
            <h3>webappuser: {appUser?.id}</h3>
            <h3>stravaAthleteId: {appUser?.stravaAthleteId}</h3>
          </div>
        </div>
      </main>
    </>
  );
}

export default SegmentSniper;

User控制器代码

namespace StravaSegmentSniper.React.Controllers
{
    [Authorize]
    [Route("[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        private readonly IWebAppUserService _webAppUserService;
        private readonly UserManager<WebAppUser> _userManager;
        private readonly IHttpContextAccessor _httpContextAccessor;

        public UserController(IWebAppUserService webAppUserService, UserManager<WebAppUser> userManager, IHttpContextAccessor httpContextAccessor)
        {
            _webAppUserService = webAppUserService;
            _userManager = userManager;
            _httpContextAccessor = httpContextAccessor;
        }


        [HttpGet]
        public WebAppUser GetUser()
        {
            var userId = _httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier).ToString();
             var user = _webAppUserService.GetLoggedInUserById(userId);
            return user;
        }
}

Program.cs代码

using StravaSegmentSniper.React.Helpers;

//var builder = WebApplication.CreateBuilder(args);
var builder = WebAppBuilderConfig.ConfigureBuilder();

var app = builder.Build();


// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}


app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();

app.UseSwagger();

app.UseSwaggerUI();

app.MapControllers();

app.MapRazorPages();

app.MapFallbackToFile("index.html"); ;


app.Run();

WebAppBuilderConfig代码

using Authorization.Data.Data;
using Authorization.Data.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using StravaSegmentSniper.Data;
using StravaSegmentSniper.React.ActionHandlers.Activity;
using StravaSegmentSniper.React.ActionHandlers.Segment;
using StravaSegmentSniper.React.ActionHandlers.StravaApiToken;
using StravaSegmentSniper.Services.Internal.Adapters;
using StravaSegmentSniper.Services.Internal.Services;
using StravaSegmentSniper.Services.StravaAPI.Activity;
using StravaSegmentSniper.Services.StravaAPI.Athlete;
using StravaSegmentSniper.Services.StravaAPI.Segment;
using StravaSegmentSniper.Services.StravaAPI.TokenService;
using System.Configuration;
using System.Text;

namespace StravaSegmentSniper.React.Helpers
{
    public class WebAppBuilderConfig
    {
        public static WebApplicationBuilder ConfigureBuilder()
        {
            var builder = WebApplication.CreateBuilder();


            var authConnectionString = builder.Configuration.GetConnectionString("AuthorizationData");
            builder.Services.AddDbContext<AuthDbContext>(options =>
                options.UseSqlServer(authConnectionString));
            builder.Services.AddDatabaseDeveloperPageExceptionFilter();

            builder.Services.AddDefaultIdentity<WebAppUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<AuthDbContext>();

            builder.Services.AddIdentityServer()
                .AddApiAuthorization<WebAppUser, AuthDbContext>();

            builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddIdentityServerJwt();

            var appDataConnectionString = builder.Configuration.GetConnectionString("StravaSegmentSniperData");
            builder.Services.AddDbContext<StravaSegmentSniperDbContext>(options =>
                options.UseSqlServer(appDataConnectionString).UseLazyLoadingProxies());

            builder.Services.AddControllersWithViews();

            builder.Services.AddRazorPages();

            builder.Services.AddControllers().AddNewtonsoftJson();

            //builder.Services.AddRouting(ctx => ctx.LowercaseUrls = false);

            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Title = "JWTToken_Auth_API",
                    Version = "v1"
                });
                c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.ApiKey,
                    Scheme = "Bearer",
                    BearerFormat = "JWT",
                    In = ParameterLocation.Header,
                    Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 1safsfsdfdfd\"",
                });
                c.AddSecurityRequirement(new OpenApiSecurityRequirement {
        {
            new OpenApiSecurityScheme {
                Reference = new OpenApiReference {
                    Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                }
            },
            new string[] {}
        }
    });
            });

            builder.Services.AddAutoMapper(typeof(Program));

            //builder.Services.AddScoped<IStravaSegmentSniperDbContext>(provider => provider.GetService<StravaSegmentSniperDbContext>());

            //add DI services to the container
            builder.Services.AddScoped<IAthleteActivityService, AthleteActivityService>();
            builder.Services.AddScoped<IAthleteService, AthleteService>();
            builder.Services.AddScoped<IStravaTokenService, StravaTokenService>();
            builder.Services.AddScoped<IWebAppUserService, WebAppUserService>();
            builder.Services.AddScoped<IStravaAPIAthlete, StravaAPIAthlete>();
            builder.Services.AddScoped<IStravaAPIActivity, StravaAPIActivity>();
            builder.Services.AddScoped<IStravaAPISegment, StravaAPISegment>();
            builder.Services.AddScoped<IStravaAPIToken, StravaAPIToken>();
            builder.Services.AddScoped<IActivityAdapter, ActivityAdapter>();
            builder.Services.AddScoped<ISegmentAdapter, SegmentAdapter>();
            builder.Services.AddScoped<IStravaActivityActionHandler, StravaActivityActionHandler>();
            builder.Services.AddScoped<IStravaApiTokenActionHandler, StravaApiTokenActionHandler>();
            builder.Services.AddScoped<IStravaSegmentActionHandler, StravaSegmentActionHandler>();

            return builder;
        }
    }
}

内容的提问来源于stack exchange,提问作者TylerBobe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 04:52:35