在 ASP.NET Web Forms 上使用 OAuth

最近遇到舊系統要串接 OAuth,發現有些方法不像在 ASP.NET Core 上,有 Middleware 可以用,所以記錄一下,用 .NET Framework 4.8 的專案為範例。

使用 OWIN 串接 OAuth

  1. 安裝 Microsoft.Owin Nuget Packages

    版本為 4.2.2

    1
    2
    3
    4
    Install-Package Microsoft.Owin.Host.SystemWeb
    Install-Package Microsoft.Owin.Security
    Install-Package Microsoft.Owin.Security.Cookies
    Install-Package Microsoft.Owin.Security.OpenIdConnect
  2. 新增 OWIN Startup class

    安裝 Microsoft.Owin.Host.SystemWeb 後就會多出這個選項

  3. Startup.cs 新增下列程式碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    public class Startup
    {
    private readonly string _authority = ConfigurationManager.AppSettings["Auth_Authority"];
    private readonly string _clientId = ConfigurationManager.AppSettings["Auth_ClientId"];
    private readonly string _clientSecret = ConfigurationManager.AppSettings["Auth_ClientSecret"];
    private readonly string _redirectUri = ConfigurationManager.AppSettings["Auth_RedirectUri"];

    public void Configuration(IAppBuilder app)
    {
    ConfigureAuth(app);
    }

    private void ConfigureAuth(IAppBuilder app)
    {
    app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
    LoginPath = new PathString("/Login")
    });

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
    Authority = _authority,
    ClientId = _clientId,
    ClientSecret = _clientSecret,
    RedeemCode = true,
    RedirectUri = _redirectUri,
    ResponseType = OpenIdConnectResponseType.Code,
    SaveTokens = true
    });
    }
    }

    配置 LoginPath = new PathString("/Login"),Middleware 會以 302 Found 重新導向 401 Unauthorized 到登入頁面。

  4. Global.asax.cs 新增下列程式碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class Global : HttpApplication
    {
    void Application_Start(object sender, EventArgs e)
    {
    // Code that runs on application startup
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
    }

    void Application_AuthenticateRequest(object sender, EventArgs e)
    {
    var requestPath = Context.Request.FilePath;
    var isLoginPath = requestPath.IndexOf("/Login", 0, StringComparison.OrdinalIgnoreCase) != -1;
    var isCallbackPath = requestPath.IndexOf("/signin-oidc", 0, StringComparison.OrdinalIgnoreCase) != -1;

    var hasUser = User != null;
    var isAuthenticated = hasUser && User.Identity.IsAuthenticated;

    if (!isLoginPath && !isCallbackPath && !isAuthenticated)
    {
    Response.StatusCode = (int)HttpStatusCode.Unauthorized;
    Response.End();
    }
    }
    }

    配置 Global Authorization Policy,讓系統預設為必須經過驗證。在 ASP.NET Core 上可以使用 Require authenticated users,在 ASP.NET Web Forms 上目前沒有找到方法,所以自行實作。

  5. Login.aspx.cs 新增下列程式碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public partial class Login : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    if (!IsPostBack)
    {
    if (!User.Identity.IsAuthenticated)
    {
    var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
    authenticationManager.Challenge();
    return;
    }

    var returnUrl = Request.QueryString["ReturnUrl"];
    if (returnUrl != null)
    {
    Response.Redirect(returnUrl);
    }
    else
    {
    Response.Redirect("/Default");
    }
    }
    }
    }

    使用 HttpContext.Current.GetOwinContext().Authentication.Challenge() 就會根據 Startup 的配置,自動導向到 Identity Provider 進行驗證。
    如果要登出的話,使用 HttpContext.Current.GetOwinContext().Authentication.SignOut()

    登入成功後會產生下列 Cookies

使用 Access Token 呼叫 Web API

Startup 中,如果有設定 SaveTokens = true,會把 Token 存在 Cookie 裡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (User.Identity.IsAuthenticated)
{
// Get access token from cookie
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
var authenticationResult = await authenticationManager.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType);
var accessToken = authenticationResult.Properties.Dictionary["access_token"];

// Call protected APIs using access token
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var responseBody = await httpClient.GetStringAsync("yourRequestUri");
// ...
}
}