.NET
Package: Tenon.Auth 1.0.0.
- Targets: .NET 8+, and .NET Standard 2.0 for .NET Framework 4.7.2+.
- Dependencies: none on .NET 8+; System.Text.Json on .NET Standard 2.0.
- Thread safety: the client is thread-safe; register it as a singleton.
Install
Section titled “Install”Download the package from Downloads, check its SHA-256, put it in a folder, and add it as a local source:
dotnet nuget add source ./packages --name localdotnet add package Tenon.Auth --version 1.0.0Configure (ASP.NET Core)
Section titled “Configure (ASP.NET Core)”builder.Services.AddSingleton(new TenonAuthClient(new TenonAuthClientOptions{ BaseUrl = builder.Configuration["TenonAuth:Url"], // https://api.glow-kin.com ApiKey = builder.Configuration["TenonAuth:ApiKey"], // user secrets, Key Vault}));// The visitor's IP address behind your load balancer:builder.Services.Configure<ForwardedHeadersOptions>(o => o.ForwardedHeaders = ForwardedHeaders.XForwardedFor);Options:
Timeout(30 s) andConnectTimeout(5 s);Headers;- your own
HttpClient:new TenonAuthClient(options, httpClient), e.g. fromIHttpClientFactory, with redirects off.
Sign in
Section titled “Sign in”[HttpPost("/login")]public async Task<IActionResult> Login(LoginForm form){ var account = await _accounts.CheckPasswordAsync(form.Username, form.Password); // yours SecondStep step; try { step = await _tenonAuth.BeginSecondStepAsync(account.Id.ToString(), new SignInContext(HttpContext.Connection.RemoteIpAddress?.ToString(), Request.Headers.UserAgent), Request.Cookies["tenon_device"]); } catch (TenonAuthUnavailableException) { return EmergencyOrRefuse(account); // fail closed } HttpContext.Session.SetString("SecondStep", JsonSerializer.Serialize(step)); return step.Kind switch { SecondStepKind.CodeEntry => Redirect("/login/code"), SecondStepKind.PushWait => Redirect("/login/approve"), SecondStepKind.RecoveryOnly => Redirect("/login/recover"), _ => await SignInAsync(account), // Trusted or NoMethod };}
[HttpPost("/login/code")]public async Task<IActionResult> Code(string code){ var step = JsonSerializer.Deserialize<SecondStep>(HttpContext.Session.GetString("SecondStep")!)!; var result = await _tenonAuth.CompleteWithCodeAsync(step, code); if (result.Approved) return await SignInAsync(step.UserId); if (result.Status == SecondStepStatus.WrongCode) return Redirect($"/login/code?left={result.AttemptsRemaining}"); return Redirect("/login"); // Failed, Expired}The waiting page calls PollApprovalAsync(step) every 2 seconds. The recovery page calls CompleteWithRecoveryCodeAsync(step, code).
Errors
Section titled “Errors”All errors are TenonAuthException subclasses:
TenonAuthUnavailableException(Reason);ApiKeyRejectedException,ForbiddenException;InvalidRequestException,NotFoundException,InvalidCodeException;FactorLockedException,TooManyRequestsException;EmailRefusedException,EmailUnavailableException;ConflictException.
See Errors.