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

ASP.NET Core+React JWT认证提交表单时出现userId required错误求助

问题:JWT认证下提交表单返回"userId is required" 400错误

我正在开发一个需用户登录才能提交表单的应用,采用JWT认证并将token存储在sessionStorage中(也曾尝试本地存储)。登录后控制台可看到token,进入提交表单页面后token仍有效,但提交表单时返回400错误“userId is required”,尽管控制台显示表单使用的token与登录时一致。我已尝试调整Program.cs中Identity与JWT配置的顺序、修改Controller的Authorize属性,但问题仍未解决。

用户控制器代码(C#)

public UserController(HelpDeskDbContext context, UserManager<User> userManager, SignInManager<User> signInManager, IConfiguration configuration)
{
    _context = context;
    _userManager = userManager;
    _signInManager = signInManager;
    _jwtKey = configuration["Jwt:Key"];
}

[Authorize(AuthenticationSchemes = "Bearer")]
[HttpPost("createComplaint")]
public async Task<IActionResult> CreateComplaint([FromBody] ComplaintForm complaintForm)
{
    var userId = _userManager.GetUserId(User);
    complaintForm.UserId = userId;
    _context.ComplaintForms.Add(complaintForm);
    await _context.SaveChangesAsync();
    return Ok(complaintForm);
}

[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = await _userManager.FindByEmailAsync(model.Email);
        if (user != null)
        {
            var passwordValid = await _userManager.CheckPasswordAsync(user, model.Password);
            if (passwordValid)
            {
                var token = GenerateJwtToken(user);
                return Ok(new { token, Role = user.Role });
            }
        }
        return Unauthorized("Invalid login attempt.");
    }
    return BadRequest("Invalid login attempt.");
}  

private string GenerateJwtToken(User user)
{
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.UTF8.GetBytes(_jwtKey);
    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Name, user.UserName), // Optionally add more claims
        }),
        Expires = DateTime.UtcNow.AddDays(7),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
        Issuer = "YourIssuer", // Ensure these match in your token validation settings
        Audience = "YourAudience"
    };
    var token = tokenHandler.CreateToken(tokenDescriptor);
    return tokenHandler.WriteToken(token);
}  

public class RegisterViewModel
{
    public string? Email { get; set; }
    public string? Password { get; set; }
}

public class LoginViewModel
{
    public string? Email { get; set; }
    public string? Password { get; set; }
    public bool RememberMe { get; set; }
}

实体类代码(C#)

public class ComplaintForm
{
    public int Id { get; set; }
    public string? Subject { get; set; }
    public string? Description { get; set; }
    public string? FilePath { get; set; }
    public FormStatus Status { get; set; }
    public string UserId { get; set; }
    public string? AssignedWorkerId { get; set; }
    public DateTime DueDate { get; set; }

    [JsonIgnore]
    public virtual User? User { get; set; }
    [JsonIgnore]
    public virtual User? AssignedWorker { get; set; }
    [JsonIgnore]
    public virtual ICollection<Comment>? Comments { get; set; }
}  

public class User : IdentityUser
{
    public string? Role { get; set; }
    public virtual ICollection<ComplaintForm>? ComplaintForms { get; set; }
    public virtual ICollection<ComplaintForm>? AssignedComplaintForms { get; set; }
}

appsettings.json配置

"Jwt": {
    "Key": "my key",
    "Issuer": "YourIssuer",
    "Audience": "YourAudience"
},

Program.cs代码(C#)

var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]);

builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.RequireHttpsMetadata = false;
    options.SaveToken = true;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuerSigningKey = true,
        IssuerSigningKey = new SymmetricSecurityKey(key),
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidIssuer = jwtSettings["Issuer"],
        ValidAudience = jwtSettings["Audience"]
    };
});

// Add services to the container.
builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDbContext<mydbcontext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("mydbcontext")));

builder.Services.AddIdentity<User, IdentityRole>()
    .AddEntityFrameworkStores<HelpDeskDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.MaxDepth = 64;
});

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowSpecificOrigin", builder =>
    {
        builder.WithOrigins("http://localhost:mylocalhost")
               .AllowAnyMethod()
               .AllowAnyHeader()
               .AllowCredentials();
    });
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Apply the CORS policy
app.UseCors("AllowSpecificOrigin");

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

app.MapControllers();

app.Run();

React前端代码

api.js

export const api = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json'
  },
});

api.interceptors.request.use((config) => {
  const token = sessionStorage.getItem('authToken');
  if (token) {
    config.headers['Authorization'] = `Bearer ${token}`;
  }
  return config;
}, (error) => {
  return Promise.reject(error);
});

// Authentication
export const register = (data) => api.post('/user/register', data);

export const login = async (data) => {
  try {
    const response = await api.post('/user/login', data);
    return {
        token: response.data.token, // Ensure this matches your API response structure
        role: response.data.role,   // Ensure this matches your API response structure
      };
  } catch (error) {
    console.error('Login failed', error);
    throw error;
  }
};

// User Complaints
export const createComplaint = (data) => api.post('/user/createComplaint', data);

export const getUserComplaints = async () => {
  try {
    const response = await api.get('/user/complaints');
    return response.data;
  } catch (error) {
    console.error('Failed to get user complaints', error);
    throw error;
  }
};

auth.js、Login组件、ComplaintForm组件

export const isAuthenticated = () => {
  const token = sessionStorage.getItem('authToken');
  return token !== null;
};

import React, { useState } from 'react';
import { login } from '../api'; // Adjust the import path as needed
import { useNavigate } from 'react-router-dom';

function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const navigate = useNavigate(); // Initialize useNavigate hook

  const handleLogin = async (e) => {
    e.preventDefault();
    try {
      const { token, role } = await login({ email, password });
      console.log('Login successful:', token);

      // Store the token in sessionStorage
      sessionStorage.setItem('authToken', token);

      // Redirect based on the role
      if (role === 'Admin') {
        navigate('/admin');
      } else if (role === 'Worker') {
        navigate('/worker'); // Redirect to worker dashboard
      } else {
        navigate('/'); // Redirect to the homepage or user dashboard
      }
    } catch (err) {
      setError('Login failed. Please check your credentials and try again.');
    }
  };

  return (
    <div>
      <h2>Login</h2>
      <form onSubmit={handleLogin}>
        <div>
          <label>Email:</label>
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </div>
        <div>
          <label>Password:</label>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            required
          />
        </div>
        <button type="submit">Login</button>
      </form>
      {error && <p>{error}</p>}
    </div>
  );
}

export default Login;

import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { createComplaint } from '../api'; 

const ComplaintForm = () => {
  const [subject, setSubject] = useState('');
  const [description, setDescription] = useState('');
  const [filePath, setFilePath] = useState('');
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
  
    const data = {
      subject,
      description,
      filePath
    };
  
    try {
      await createComplaint(data);
      navigate('/success'); 
    } catch (error) {
      console.error('Error submitting complaint', error);
      
      if (error.response && error.response.data) {
        console.error('Error response:', error.response.data);
      } else {
        console.error('Error message:', error.message);
      }
      alert('Failed to submit complaint');
    }
  };
  

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Subject</label>
        <input
          type="text"
          value={subject}
          onChange={(e) => setSubject(e.target.value)}
          required
        />
      </div>
      <div>
        <label>Description</label>
        <textarea
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          required
        ></textarea>
      </div>
      <div>
        <label>File Path</label>
        <input
          type="text"
          value={filePath}
          onChange={(e) => setFilePath(e.target.value)}
        />
      </div>
      <button type="submit">Submit Complaint</button>
    </form>
  );
};

export default ComplaintForm;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:55:53