AnointedAutomation.SSO.Owin 1.0.1

dotnet add package AnointedAutomation.SSO.Owin --version 1.0.1
                    
NuGet\Install-Package AnointedAutomation.SSO.Owin -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AnointedAutomation.SSO.Owin" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AnointedAutomation.SSO.Owin" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="AnointedAutomation.SSO.Owin" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AnointedAutomation.SSO.Owin --version 1.0.1
                    
#r "nuget: AnointedAutomation.SSO.Owin, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AnointedAutomation.SSO.Owin@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AnointedAutomation.SSO.Owin&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=AnointedAutomation.SSO.Owin&version=1.0.1
                    
Install as a Cake Tool

AnointedAutomation.SSO.Owin

Jesus is King ✝️

"Sign in with Anointed Automation" for classic ASP.NET on .NET Framework 4.7.2+: MVC 5 and Web Forms on OWIN/Katana. This package is a thin, opinionated layer over the standard Microsoft.Owin.Security.OpenIdConnect middleware (it does not hand-roll the protocol), plus what that middleware does not give you: a back-channel logout endpoint, a session status client and a brand sign-in button for Razor and Web Forms.

On ASP.NET Core use AnointedAutomation.SSO instead.

Issuer https://api.anointedautomation.net/
Discovery https://api.anointedautomation.net/.well-known/openid-configuration
ID tokens RS256, verified against the published JWKS
Flow Authorization code + PKCE (S256) only; state required; nonce used
Full guide Sign in with Anointed Automation: access, credentials, claims, rotation, troubleshooting

Server only. This package holds your client secret and your partner API key. Never ship either to a browser or anything else that runs on a user's device.

1. Get your credentials

Sign-in is self-serve for Tech partners. Apply at https://www.anointedautomation.net/partners/apply, then in the partner portal create an API key on the Keys tab, and on the Sign in tab turn sign-in on for that key with your exact callback URL (absolute https, exact match) and, if you want it, your back-channel logout URL. You get:

  • client_id: aa_<your key id>
  • client_secret: shown exactly once. Store it only in your server's secret store.

Steps in detail: How to get access.

2. Install

Install-Package AnointedAutomation.SSO.Owin
Install-Package Microsoft.Owin.Host.SystemWeb

Microsoft.Owin.Host.SystemWeb runs the OWIN pipeline inside IIS / IIS Express and gives you SystemWebCookieManager and HttpContext.GetOwinContext().

Binding redirects are required. The package depends on System.Text.Json, System.Memory, Microsoft.Bcl.* and Microsoft.IdentityModel.*, whose assembly versions differ across the graph. Without redirects .NET Framework throws FileNotFoundException at runtime (back-channel logout answers 500). NuGet adds them to Web.config on install; if it did not, set this in the web project's .csproj and rebuild:

<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>

Or run Add-BindingRedirect in the Package Manager Console.

3. Startup (shared by MVC 5 and Web Forms)

Startup.cs at the project root:

using AnointedAutomation.SSO.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Host.SystemWeb;
using Microsoft.Owin.Security.Cookies;
using Owin;
using System;
using System.Configuration;
using System.Threading.Tasks;

[assembly: OwinStartup(typeof(YourApp.Startup))]

namespace YourApp
{
    public class Startup
    {
        public static IAnointedSessionStatus SessionStatus;

        public void Configuration(IAppBuilder app)
        {
            // Optional: lets the cookie hook below ask whether a session is still alive (fail open).
            SessionStatus = AnointedSessionStatusClient.Create(new AnointedSessionStatusOptions
            {
                ApiKey = Required("ANOINTED_API_KEY"), // needs the sessions:status scope; server only
                AppName = "Your App",
            }, app.CreateLogger<AnointedSessionStatusClient>());

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
                CookieManager = new SystemWebCookieManager(), // avoids the classic Katana/System.Web lost-cookie bug
                CookieHttpOnly = true,
                CookieSecure = CookieSecureOption.Always,
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = context =>
                        AnointedSessionStatusCookieEvents.ValidateIdentityAsync(context, SessionStatus, grant: true),
                },
            });

            app.UseAnointedAutomation(new AnointedAutomationOptions
            {
                ClientId = Required("ANOINTED_OAUTH_CLIENT_ID"),         // aa_<your key id>
                ClientSecret = Required("ANOINTED_OAUTH_CLIENT_SECRET"), // server only
                RedirectUri = "https://www.example.com/auth/anointed/callback", // must match the registered URL
                ConfigureOpenIdConnect = oidc => oidc.CookieManager = new SystemWebCookieManager(),
            });

            app.MapAnointedBackchannelLogout("/auth/anointed/backchannel-logout", (logout, owinContext) =>
            {
                // End every session for logout.Sub (or logout.Sid) and drop any refresh token you hold.
                return YourSessionStore.EndAllAsync(logout.Sub, logout.Sid);
            });
        }

        private static string Required(string name)
        {
            // appSettings: keep the secrets out of source control (a non-committed file, or your host's app settings).
            string value = ConfigurationManager.AppSettings[name];
            if (string.IsNullOrWhiteSpace(value))
            {
                throw new InvalidOperationException("Missing required setting " + name);
            }
            return value;
        }
    }
}

appSettings keys used above:

ANOINTED_OAUTH_CLIENT_ID=aa_<your key id>
ANOINTED_OAUTH_CLIENT_SECRET=<server only>
ANOINTED_API_KEY=<server only, only for session status>

4a. MVC 5 quick start

A start action that challenges the Anointed scheme (Controllers/AuthController.cs):

using AnointedAutomation.SSO.Owin;
using Microsoft.Owin.Security;
using System.Web;
using System.Web.Mvc;

public class AuthController : Controller
{
    [AllowAnonymous]
    [Route("auth/anointed/start")]
    public ActionResult Start(string returnTo)
    {
        string target = Url.IsLocalUrl(returnTo) ? returnTo : "/";
        HttpContext.GetOwinContext().Authentication.Challenge(
            new AuthenticationProperties { RedirectUri = target },
            AnointedAutomationDefaults.AuthenticationType); // "Anointed"
        return new HttpUnauthorizedResult();
    }
}

The button in any Razor view (add <add namespace="AnointedAutomation.SSO.Owin" /> to Views/web.config or use @using AnointedAutomation.SSO.Owin):

@Html.AnointedSignInButton("~/auth/anointed/start")
@Html.AnointedSignInButton("~/auth/anointed/start", "/account")
@Html.AnointedSignInButton(new AnointedSignInButtonOptions { StartUrl = "~/auth/anointed/start", CssClass = "btn btn-dark" })

Read the user in a controller:

string sub = ((ClaimsPrincipal)User).GetAnointedSubject();   // stable user id: key your users on this
string sid = ((ClaimsPrincipal)User).GetAnointedSessionId(); // grant id, matches back-channel logout's Sid
string email = ((ClaimsPrincipal)User).GetAnointedEmail();   // verified email (null without the email scope)

4b. Web Forms quick start

A start page (Auth/Start.aspx.cs):

using AnointedAutomation.SSO.Owin;
using Microsoft.Owin.Security;
using System;
using System.Web;

public partial class Start : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        string returnTo = Request.QueryString["returnTo"];
        bool local = !string.IsNullOrEmpty(returnTo) && returnTo.StartsWith("/") && !returnTo.StartsWith("//") && !returnTo.StartsWith("/\\");
        Context.GetOwinContext().Authentication.Challenge(
            new AuthenticationProperties { RedirectUri = local ? returnTo : "/" },
            AnointedAutomationDefaults.AuthenticationType);
        Response.StatusCode = 401;
        Response.End();
    }
}

The button on any page or master page:

<%@ Register Assembly="AnointedAutomation.SSO.Owin" Namespace="AnointedAutomation.SSO.Owin.WebForms" TagPrefix="anointed" %>

<anointed:AnointedSignInButton runat="server" StartUrl="~/Auth/Start.aspx" ReturnTo="/Account.aspx" />

(Or register the tag once in web.config under system.web/pages/controls.) Read the user with Context.GetOwinContext().Authentication.User.GetAnointedSubject().

What UseAnointedAutomation sets

Setting Value
Authority https://api.anointedautomation.net/ (https metadata required)
AuthenticationType Anointed (change with AuthenticationType)
ResponseType / ResponseMode code / query, RedeemCode = true
PKCE on (S256)
RedirectUri / CallbackPath your RedirectUri / its path
Scope openid email plus AdditionalScopes
Claims raw OIDC names (MapInboundClaims = false), name claim name
ID token algorithms RS256 only
SaveTokens false unless you set SaveTokens = true
Verified email with the email scope, a sign-in whose email_verified is not true is refused (RequireVerifiedEmail, default true)

ClientId (must start with aa_), ClientSecret and RedirectUri (absolute https with its own path) are required and have no defaults: a missing or invalid value throws InvalidOperationException while the app starts. Your own Notifications are kept: the verified-email check runs first, then your SecurityTokenValidated. A refused sign-in throws AnointedSignInRejectedException, which Katana hands to the AuthenticationFailed notification: handle it there, call HandleResponse(), and redirect to a generic error page. ConfigureOpenIdConnect = oidc => { ... } gets the last word on the OpenIdConnectAuthenticationOptions.

Scope advice

  • The default scope is openid email. Keep it unless you truly need more.
  • Requesting profile makes users without a username pick one before consenting. Add it only when you need it.
  • offline_access adds a refresh token; ask for it only if you call our API later (then set SaveTokens = true).
  • Key your users on sub, never on email.

Back-channel logout

app.MapAnointedBackchannelLogout(path, onLogout) answers anonymous POSTs at that path (it runs in the OWIN pipeline before MVC, so no antiforgery token is involved; other methods get 405). Register its full https URL with us. It validates the Logout Token exactly as OpenID Connect Back-Channel Logout 1.0 asks: RS256 only, signature against our JWKS (refetched on an unknown kid), typ logout+jwt, exact iss, aud containing your client_id, iat no older than 5 minutes (BackchannelLogoutMaxAge), exp not passed, the back-channel event, a jti, sub and/or sid, and no nonce, with 60 seconds of clock skew (ClockSkew).

Situation Answer onLogout called
Valid token 200 yes, once
Same jti again (our retry) 200 no
Invalid token, or no logout_token 400 no
onLogout throws, or our keys cannot be read 500 (we retry with the same jti) the retry runs it again

Processed jti values are remembered in memory until the token expires. On more than one server, set AnointedAutomationOptions.LogoutReplayStore to your own IAnointedLogoutReplayStore (for example over Redis).

Session status (fail open)

IAnointedSessionStatus.CheckAsync(userId, grant, cancellationToken) asks GET https://api.anointedautomation.net/api/session-status?userId=<sub>&grant=<true|false> whether a session may continue. Your API key needs the sessions:status scope (ask us to add it). The client exchanges the key for a machine token, caches it, re-exchanges once on a 401, times out each request after 3 seconds and reuses each answer for 5 minutes. Create ONE client per app with AnointedSessionStatusClient.Create(options, logger).

It FAILS OPEN. Only a 200 that says active: false is Ended. A network error, a timeout, any other status or an unreadable body is Unknown: not cached, logged, and you keep the session. AnointedSessionStatusCookieEvents.ValidateIdentityAsync(context, status, grant) is a ready OnValidateIdentity hook: only on Ended does it reject the identity and sign the cookie out.

Security checklist

  • The client secret and API key live only in your server's secret store (not in a committed web.config).
  • Your redirect URI is https and registered exactly.
  • Users are keyed on sub, never on email or username.
  • Session cookies are HttpOnly, Secure, and use SystemWebCookieManager.
  • AuthenticationFailed shows a generic message and never echoes the raw provider error.
  • You honor revocation: a back-channel logout (or an Ended session status) ends the user's session.
  • Your server clock is NTP-synced.

License

MIT. Copyright © Anointed Automation, LLC. Stewarded by Alexander Fields. Jesus is King ✝️

Product Compatible and additional computed target framework versions.
.NET Framework net472 is compatible.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 55 10/26/2026
1.0.0 59 10/24/2026