Skip to content

Conversation

@jeremyZX
Copy link

@jeremyZX jeremyZX commented Aug 1, 2025

I found SmtpServer while trying to get my team off of Papercut, since Papercut forces developers to write SMTP-facing code that supports cleartext and no authentication. SmtpServer met all of my needs, with the exception of OAUTHBEARER and XOAUTH2 authentication mechanisms.

This PR adds support for SASL XOAUTH2 and SASL OAUTHBEARER, since I need something that will respond positively to those login requests in a bench testing scenario.

I added support for XOAUTH2 and OAUTHBEARER by adding separate BearerTokenAuthenticator related authentication classes. There are many parallels to the existing PasswordAuthenticator classes, but ultimately I thought it would be beneficial to keep them separate in case someone wants to enable a server that supports both AUTH PLAIN/LOGIN and AUTH XOAUTH2/OAUTHBEARER.

One part I was not entirely sure about is how to decode AUTH LOGIN XOAUTH2 within SmtpParser. It seemed like I should have been able to call reader.TryMake(TryMakeTextOrNumber, out var text) to obtain a sequence from the buffer that had both a string and number in it, but tokenization appears to isolate text strings from number strings. If there's a more idiomatic way of fetching an alphanumeric string in this case, let me know and I'll update this PR.

Here's a sample:

async Task Main(CancellationToken token)
{
  var options = new SmtpServerOptionsBuilder()
    .ServerName("localhost")
    .Port(1125)
    .Build();

  var serviceProvider = new ServiceProvider();
  serviceProvider.Add(new CustomBearerTokenAuthenticator());

  var smtpServer = new SmtpServer.SmtpServer(options, serviceProvider);

  await smtpServer.StartAsync(token);
}

public class CustomBearerTokenAuthenticator : IBearerTokenAuthenticator, IBearerTokenAuthenticatorFactory
{
  public Task<bool> AuthenticateAsync(ISessionContext context, string user, string bearerToken, CancellationToken token)
  {
    // If bearer token is a JWT,  you could load it using JwtSecurityTokenHandler and validate issuer, dates, signature, etc.
    Console.WriteLine("CustomBearerTokenAuthenticator: User is valid (User={0} Token={1})", user, bearerToken);
  
    return Task.FromResult(true);
  }
  
  public IBearerTokenAuthenticator CreateInstance(ISessionContext context)
  {
    return new CustomBearerTokenAuthenticator();
  }
}

Client code (assuming MailKit)

using var smtpClient = new SmtpClient();
await smtpClient.ConnectAsync("localhost", 1125);
var credential = new SaslMechanismOAuth2("test-user", "1234567890");
await smtpClient.AuthenticateAsync(credential);
await smtpClient.DisconnectAsync(true);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant