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

如何基于ASP.NET构建MS Graph独立登录门户,实现多PHP站点Azure SSO

Solution for Building a Shared ASP.NET MS Graph Login Portal for PHP SSO

Let's break down how to implement this shared login portal that works with your PHP sites without modifying their code, leveraging Azure AD SSO and MS Graph.

Core Approach

Instead of registering multiple Azure AD apps (one per PHP site), we'll use a single Azure AD registered application for your ASP.NET portal. The portal acts as an authentication middleman:

  1. PHP sites redirect unauthenticated users to the portal (with their return URL as a parameter).
  2. The portal handles Azure AD login (including silent SSO for returning users).
  3. Post-login, the portal redirects users back to the originating PHP site and sets a shared authentication state (cookie or token) that PHP sites can validate via web server config.

Step 1: Configure Your Azure AD Application

  1. Register a Web Application in Azure AD.
  2. Set the Redirect URI to your ASP.NET portal's callback endpoint (e.g., https://your-login-portal.com/Auth/Callback).
  3. Enable ID Tokens under the "Authentication" > "Implicit grant and hybrid flows" section (we need this for user identity data).
  4. Add the MS Graph permission User.Read (delegated) and grant admin consent.

Step 2: ASP.NET Portal Implementation (MVC Example)

We'll use Microsoft.Identity.Web to handle Azure AD authentication.

Setup Authentication in Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add Azure AD authentication
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddControllersWithViews();
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(10);
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession(); // Required to store return URL temporarily
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Auth}/{action=Login}/{id?}");

app.Run();

Auth Controller Logic

Create an AuthController to handle login, callback, and silent SSO:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class AuthController : Controller
{
    // Allowed PHP sites to prevent open redirect attacks
    private readonly List<string> _allowedReturnDomains = new()
    {
        "https://php-site1.com",
        "https://php-site2.com",
        "https://php-site3.com"
    };

    private readonly IConfiguration _config;

    public AuthController(IConfiguration config)
    {
        _config = config;
    }

    public IActionResult Login(string returnUrl)
    {
        // Check if user is already authenticated (silent SSO)
        if (User.Identity.IsAuthenticated)
        {
            if (IsValidReturnUrl(returnUrl))
            {
                return RedirectToReturnUrl(returnUrl);
            }
            return BadRequest("Invalid return URL");
        }

        // Validate return URL first to block malicious redirects
        if (!IsValidReturnUrl(returnUrl))
        {
            return BadRequest("Invalid return URL");
        }

        // Store return URL in session for post-login redirect
        HttpContext.Session.SetString("ReturnUrl", returnUrl);

        // Initiate Azure AD login flow
        return Challenge(new AuthenticationProperties
        {
            RedirectUri = "/Auth/Callback"
        }, OpenIdConnectDefaults.AuthenticationScheme);
    }

    public async Task<IActionResult> Callback()
    {
        // Get authentication result from Azure AD
        var authResult = await HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);
        if (!authResult.Succeeded)
        {
            return View("Error");
        }

        // Retrieve stored return URL from session
        var returnUrl = HttpContext.Session.GetString("ReturnUrl") ?? "/";
        HttpContext.Session.Remove("ReturnUrl");

        return RedirectToReturnUrl(returnUrl);
    }

    private bool IsValidReturnUrl(string returnUrl)
    {
        return !string.IsNullOrEmpty(returnUrl) && 
               _allowedReturnDomains.Any(domain => returnUrl.StartsWith(domain, StringComparison.OrdinalIgnoreCase));
    }

    private IActionResult RedirectToReturnUrl(string returnUrl)
    {
        // Generate a JWT token for PHP sites to validate user identity
        var token = GenerateJwtToken(User);

        // Set a cross-domain auth cookie (if sites share a parent domain)
        Response.Cookies.Append("azure-sso-token", token, new CookieOptions
        {
            HttpOnly = true,
            Secure = true,
            SameSite = SameSiteMode.None,
            Domain = ".your-shared-domain.com", // e.g., if PHP sites are *.your-shared-domain.com
            Expires = DateTime.UtcNow.AddHours(8)
        });

        // Redirect back to the PHP site with token (fallback if cookie isn't usable)
        return Redirect($"{returnUrl}?azure-sso-token={token}");
    }

    private string GenerateJwtToken(ClaimsPrincipal user)
    {
        // Use your own secret key for signing (store in config, not hardcoded!)
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Secret"]));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: "https://your-login-portal.com",
            audience: _allowedReturnDomains,
            claims: user.Claims,
            expires: DateTime.UtcNow.AddHours(8),
            signingCredentials: credentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

Step 3: PHP Site Configuration (No Code Changes)

Use your web server (Apache/Nginx) to redirect unauthenticated users to the ASP.NET portal, and validate the auth cookie/token.

Apache Example (.htaccess)

RewriteEngine On

# Redirect unauthenticated users to the login portal
RewriteCond %{HTTP_COOKIE} !azure-sso-token=.*
RewriteRule ^(.*)$ https://your-login-portal.com/Auth/Login?returnUrl=https://%{HTTP_HOST}%{REQUEST_URI} [R=302,L]

Nginx Example

server {
    listen 443 ssl;
    server_name php-site1.com;

    # Redirect unauthenticated users to login portal
    if ($cookie_azure-sso_token = "") {
        return 302 https://your-login-portal.com/Auth/Login?returnUrl=https://$host$request_uri;
    }

    # Rest of your Nginx config...
}

Key Notes for Silent SSO

  • Azure AD maintains a session cookie on login.microsoftonline.com. When a user returns to the portal from another PHP site, the portal's Challenge request will automatically use this session to authenticate the user without prompting for credentials.
  • Ensure all sites use HTTPS: Azure AD requires HTTPS for redirect URIs, and cross-domain cookies need the Secure flag.
  • Always validate return URLs to prevent open redirect attacks—never trust unfiltered user input for redirect targets.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 18:37:55