v4.0.58 Released!

v4.0.58 is another jam-packed release starting with exciting new API Key and JWT Auth Providers enabling fast, stateless and centralized Auth Services, a modernized API surface for OrmLite, new GEO capabilities in Redis, Logging for Slack, performance and memory improvements across all ServiceStack and libraries including useful utilities you can reuse to improve performance in your own Apps!

I’ll try highlight the main points but I welcome you to checkout the full v4.0.58 Release Notes when you can.

Authentication

Auth Providers that authenticate with each request (i.e. implement IAuthWithRequest) no longer persist Users Sessions to the cache, they’re just attached to the IRequest and only last for the duration of the Request. This should be a transparent change but can be reverted by setting PersistSession=true.

API Key Auth Provider

The new ApiKeyAuthProvider provides an alternative method for allowing external 3rd Parties access to
your protected Services without needing to specify a password. API Keys is the preferred approach for
many well-known public API providers used in system-to-system scenarios for several reasons:

  • Simple - It integrates easily with existing HTTP Auth functionality
  • Independent from Password - Limits exposure to the much more sensitive master user passwords that
    should ideally never be stored in plain-text.
  • Entropy - API Keys are typically much more secure than most normal User Passwords. The configurable
    default has 24 bytes of entropy (Guids have 16 bytes)
  • Performance - Thanks to their much greater entropy and independence from user-chosen passwords,
    API Keys are validated as fast as possible using a datastore Index.

Like most ServiceStack providers the new API Key Auth Provider is simple to use, integrates seamlessly with
ServiceStack existing Auth model and includes Typed end-to-end client/server support.

We’ve modeled it around Stripe API Keys and provides an alternative way to authenticate using an API Key, which can be registered with just:

Plugins.Add(new AuthFeature(...,
    new IAuthProvider[] {
        new ApiKeyAuthProvider(AppSettings),
        //...
    }));

And can persist API Keys in either of the following Auth Repositories: OrmLiteAuthRepository, RedisAuthRepository, DynamoDbAuthRepository and InMemoryAuthRepository.

Just like Stripe, API Keys can be sent in the Username of HTTP Basic Auth or as a HTTP Bearer Token. Example using .NET Service Clients:

var client = new JsonServiceClient(baseUrl) {
    Credentials = new NetworkCredential(apiKey, "")
};

var client = new JsonHttpClient(baseUrl) {
    BearerToken = apiKey
};

And HTTP Utils:

var response = baseUrl.CombineWith("/secured").GetStringFromUrl(
    requestFilter: req => req.AddBasicAuth(apiKey, ""));
    
var response = await "https://example.org/secured".GetJsonFromUrlAsync(
    requestFilter: req => req.AddBearerToken(apiKey));

Multiple API Key Types and Environments

API Keys are automatically created when a User is registered, a key is created for each Key Type and Environment. By default it creates a “secret” API Key for both “live” and “test” environments, you could change this to also create “publishable” API Keys as well with:

Plugins.Add(new AuthFeature(...,
    new IAuthProvider[] {
        new ApiKeyAuthProvider(AppSettings) {
            KeyTypes = new[] { "secret", "publishable" },
        }
    });

If preferred properties can also be set in AppSettings:

<add key="apikey.KeyTypes" value="secret,publishable" />

Multitenancy by API Keys

Thanks to the ServiceStack’s trivial support for Multitenancy you can easily change which Database your Services and AutoQuery Services use based on Key Environment by overriding GetDbConnection() in your AppHost, e.g:

public override IDbConnection GetDbConnection(IRequest req = null)
{
    //If an API Test Key was used return DB connection to TestDb instead: 
    return req.GetApiKey()?.Environment == "test"
        ? TryResolve<IDbConnectionFactory>().OpenDbConnection("TestDb")
        : base.GetDbConnection(req);
}

JWT Auth Provider

Even more exciting than the new API Key Provider is the new integrated Auth solution for the popular
JSON Web Tokens (JWT) industry standard which is easily enabled by registering
the JwtAuthProvider with the AuthFeature plugin:

Plugins.Add(new AuthFeature(...,
    new IAuthProvider[] {
        new JwtAuthProvider(AppSettings) { AuthKey = AesUtils.CreateKey() },
        new CredentialsAuthProvider(AppSettings),
        //...
    }));

JWT Overview

A nice property of JWT tokens is that they allow for truly stateless authentication where API Keys and user
credentials can be maintained in a decentralized Auth Service that’s kept isolated from the rest of your
System, making them optimal for use in Microservice architectures.

Being self-contained lends JWT tokens to more scalable, performant and flexible architectures as they don’t
require any I/O or any state to be accessed from App Servers to validate the JWT Tokens, this is unlike all
other Auth Providers which requires at least a DB, Cache or Network hit to authenticate the user.

A good introduction into JWT is availble from the JWT website: JSON Web Token Introduction - jwt.io

Service Client Integration

Just like API Keys JWT Tokens can be sent as a HTTP Bearer Token, since we control the Service Client we’re also able to enable high-level functionality like being able to transparently handle when our JWT Token expires in order to fetch a new one. Since JWT Tokens are self-contained they could instead need to retrieved from an externalized central authority service independent from the Service we’re talking to. We can support this scenario by handling the OnAuthenticationRequired callback where we can call the Auth Service to fetch our new token with:

var authClient = JsonServiceClient(centralAuthBaseUrl) {
    Credentials = new NetworkCredential(apiKey, "")
};

var client = new JsonServiceClient(baseUrl);
client.OnAuthenticationRequired = () => {
    client.BearerToken = authClient.Send(new Authenticate()).BearerToken;
};

JWT Signature

JWT Tokens are possible courtesy of the cryptographic signature added to the end of the message that’s used
to Authenticate and Verify that a Message hasn’t been tampered with. The JWT standard allows for a number of different Hashing Algorithms although requires at least the HM256 HMAC SHA-256 to be supported which is the default. The full list of Symmetric HMAC and Asymmetric RSA Algorithms JwtAuthProvider supports include:

  • HM256 - Symmetric HMAC SHA-256 algorithm
  • HS384 - Symmetric HMAC SHA-384 algorithm
  • HS512 - Symmetric HMAC SHA-512 algorithm
  • RS256 - Asymmetric RSA with PKCS#1 padding with SHA-256
  • RS384 - Asymmetric RSA with PKCS#1 padding with SHA-384
  • RS512 - Asymmetric RSA with PKCS#1 padding with SHA-512

HMAC is the simplest to use as it lets you use the same AuthKey to Sign and Verify the message.

But if preferred you can use a RSA Keys to sign and verify tokens by changing the HashAlgorithm and
specifying a RSA Private Key:

new JwtAuthProvider(AppSettings) { 
    HashAlgorithm = "RS256",
    PrivateKeyXml = AppSettings.GetString("PrivateKeyXml") 
}

Encrypted JWE Tokens

Something that’s not immediately obvious is that while JWT Tokens are signed to prevent tampering and
verify authenticity, they’re not encrypted and can easily be read by decoding the URL-safe Base64 string.
This is a feature of JWT where it allows Client Apps to inspect the User’s claims and hide functionality
they don’t have access to, it also means that JWT Tokens are debuggable and can be inspected for whenever
you need to track down unexpected behavior.

But there may be times when you want to embed sensitive information in your JWT Tokens in which case you’ll
want to enable Encryption, which can be done with:

new JwtAuthProvider(AppSettings) { 
    PrivateKeyXml = AppSettings.GetString("PrivateKeyXml"),
    EncryptPayload = true
}

When turning on encryption, tokens are instead created following the JSON Web Encryption (JWE) standard where they’ll be encoded in the 5-part JWE Compact Serialization format.

Stateless Auth Microservices

One of JWT’s most appealing features is its ability to decouple the System that provides User Authentication Services and issues tokens from all the other Systems but are still able provide protected Services although no longer needs access to a User database or Session data store to facilitate it, as sessions can now be embedded in Tokens and its state maintained and sent by clients instead of accessed from each App Server. This is ideal for Microservice architectures where Auth Services can be isolated into a single externalized System.

With this use-case in mind we’ve decoupled JwtAuthProvider in 2 classes:

  • JwtAuthProviderReader -
    Responsible for validating and creating Authenticated User Sessions from tokens
  • JwtAuthProvider -
    Inherits JwtAuthProviderReader to also be able to Issue, Encrypt and provide access to tokens

Services only Validating Tokens

This lets us configure our Microservices that we want to enable Authentication via JWT Tokens down to just:

public override void Configure(Container container)
{
    Plugins.Add(new AuthFeature(() => new AuthUserSession(),
        new IAuthProvider[] {
            new JwtAuthProviderReader(AppSettings) {
                HashAlgorithm = "RS256",
                PublicKeyXml = AppSettings.GetString("PublicKeyXml")
            },
        }));
}

Which no longer needs access to a IUserAuthRepository or Sessions since they’re populated entirely from JWT Tokens. Whilst you can use the default HS256 HashAlgorithm, RSA is ideal for this use-case as you can limit access to the PrivateKey to only the central Auth Service issuing the tokens and then only distribute the PublicKey to each Service which needs to validate them.

Ajax Clients

Using Cookies is the recommended way for using JWT Tokens in Web Applications since the HttpOnly Cookie flag will prevent it from being accessible from JavaScript making them immune to XSS attacks whilst the Secure flag will ensure that the JWT Token is only ever transmitted over HTTPS.

You can convert your Session into a Token and set the ss-jwt Cookie in your web page by sending an Ajax request to /session-to-token, e.g:

$.post("/session-to-token");

Likewise this API lets you convert Sessions created by any of the OAuth providers into a stateless JWT Token.

OrmLite

Cleaner, Modernized API Surface

As mentioned in the last release we’ve moved OrmLite’s deprecated APIs into the ServiceStack.OrmLite.Legacy namespace leaving a clean, modern API surface in OrmLite’s default namespace.

This primarily affects the original OrmLite APIs ending with *Fmt which were used to provide a familiar API for C# developers based on C#'s string.Format(), e.g:

var tracks = db.SelectFmt<Track>("Artist = {0} AND Album = {1}", 
    "Nirvana", "Nevermind");

Whilst you can continue using the legacy API by adding the ServiceStack.OrmLite.Legacy namespace, it’s also a good time to consider switching using any of the recommended parameterized APIs below:

var tracks = db.Select<Track>(x => x.Artist == "Nirvana" && x.Album == "Nevermind");

var q = db.From<Track>()
    .Where(x => x.Artist == "Nirvana" && x.Album == "Nevermind");
var tracks = db.Select(q);

var tracks = db.Select<Track>("Artist = @artist AND Album = @album", 
    new { artist = "Nirvana", album = "Nevermind" });

var tracks = db.SqlList<Track>(
    "SELECT * FROM Track WHERE Artist = @artist AND Album = @album",
    new { artist = "Nirvana", album = "Nevermind" });

Parameterized by default

The OrmLiteConfig.UseParameterizeSqlExpressions option that could be used to disable parameterized
SqlExpressions and revert to using in-line escaped SQL has been removed in along with all its dependent
functionality, so now all queries just use db params.

Improved partial Updates and Inserts APIs

One of the limitations we had with using LINQ Expressions was the lack of support for assignment expressions which meant we previously needed to do capture which fields you wanted updated in partial updates, e.g:

db.UpdateOnly(new Poco { Age = 22 }, onlyFields:p => p.Age, where:p => p.Name == "Justin Bieber");

//increments age by 1
db.UpdateAdd(new Poco { Age = 1 }, onlyFields:p => p.Age, where:p => p.Name == "Justin Bieber");

Taking a leaf from PocoDynamo we’ve added a better API using a lambda expression which now saves us from having to specify which fields to update twice since we’re able to infer them from the returned Member Init Expression, e.g:

db.UpdateOnly(() => new Poco { Age = 22 }, where: p => p.Name == "Justin Bieber");

//increments age by 1
db.UpdateAdd(() => new Poco { Age = 1 }, where: p => p.Name == "Justin Bieber");

With async equivalents also available:

await db.UpdateOnlyAsync(() => new Poco { Age = 22 }, where: p => p.Name == "Justin Bieber");
await db.UpdateOnlyAsync(() => new Poco { Age = 1 }, where: p => p.Name == "Justin Bieber");

This feature is extended for partial INSERT’s as well:

db.InsertOnly(() => new Poco { Name = "Justin Bieber", Age = 22 });

await db.InsertOnlyAsync(() => new Poco { Name = "Justin Bieber", Age = 22 });

New ColumnExists API

We’ve added support for a Typed ColumnExists API across all supported RDBMS’s which makes it easy to
inspect the state of an RDBMS Table which can be used to determine what modifications you want on it, e.g:

db.DropColumn<Poco>(x => x.Ssn);
db.ColumnExists<Poco>(x => x.Ssn); //= false

if (!db.ColumnExists<Poco>(x => x.Age)) //= false
    db.AddColumn<Poco>(x => x.Age);
db.ColumnExists<Poco>(x => x.Age); //= true

New SelectMulti API

Previously the only Typed API available to select data across multiple joined tables was to use a Custom POCO with all the columns you want from any of the joined tables, e.g:

List<FullCustomerInfo> customers = db.Select<FullCustomerInfo>(
    db.From<Customer>().Join<CustomerAddress>());

The new SelectMulti API now lets you use your existing POCO’s to access results from multiple joined tables by returning them in a Typed Tuple:

var q = db.From<Customer>()
    .Join<Customer, CustomerAddress>()
    .Join<Customer, Order>()
    .Where(x => x.CreatedDate >= new DateTime(2016,01,01))
    .And<CustomerAddress>(x => x.Country == "Australia");

var results = db.SelectMulti<Customer, CustomerAddress, Order>(q);

foreach (var tuple in results)
{
    Customer customer = tuple.Item1;
    CustomerAddress custAddress = tuple.Item2;
    Order custOrder = tuple.Item3;
}

We’ve also added support for Select<dynamic> providing an alternative way to fetch data from multiple tables, e.g:

var q = db.From<Employee>()
    .Join<Department>()
    .Select<Employee, Department>((e, d) => new { e.FirstName, e.LastName, d.Name });
    
List<dynamic> results = db.Select<dynamic>(q);

foreach (dynamic result in results)
{
    string firstName = result.FirstName;
    string lastName = result.LastName;
    string deptName = result.Name;
}

CustomSelect Attribute

The new [CustomSelect] can be used to define properties you want populated from a Custom SQL Function or Expression instead of a normal persisted column, e.g:

public class Block
{
    public int Id { get; set; }
    public int Width { get; set; }
    public int Height { get; set; }

    [CustomSelect("Width * Height")]
    public int Area { get; set; }

    [Default(OrmLiteVariables.SystemUtc)]
    public DateTime CreatedDate { get; set; }

    [CustomSelect("FORMAT(CreatedDate, 'yyyy-MM-dd')")]
    public string DateFormat { get; set; }
}

db.Insert(new Block { Id = 1, Width = 10, Height = 5 });

var block = db.SingleById<Block>(1);

block.Area.Print(); //= 50

block.DateFormat.Print(); //= 2016-06-08

New Redis GEO Operations

The latest release of Redis 3.2.0 brings it exciting new GEO capabilities which will let you store Lat/Long coordinates in Redis and query locations within a specified radius.

To demonstrate this functionality we’ve created a new Redis GEO Live Demo which lets you click on anywhere in the U.S. to find the list of nearest cities within a given radius:

Live Demo: http://redisgeo.servicestack.net

Slack Logger

The new Slack Logger can be used to send Logging to a custom Slack Channel which is a nice interactive way
for your development team on Slack to see and discuss logging messages as they come in.

To start using it first download it from NuGet:

PM> Install-Package ServiceStack.Logging.Slack

Then configure it with the channels you want to log it to, e.g:

LogManager.LogFactory = new SlackLogFactory("{GeneratedSlackUrlFromCreatingIncomingWebhook}", 
    debugEnabled:true)
{
    //Alternate default channel than one specified when creating Incoming Webhook.
    DefaultChannel = "other-default-channel",
    //Custom channel for Fatal logs. Warn, Info etc will fallback to DefaultChannel or 
    //channel specified when Incoming Webhook was created.
    FatalChannel = "more-grog-logs",
    //Custom bot username other than default
    BotUsername = "Guybrush Threepwood",
    //Custom channel prefix can be provided to help filter logs from different users or environments. 
    ChannelPrefix = System.Security.Principal.WindowsIdentity.GetCurrent().Name
};

LogManager.LogFactory = new SlackLogFactory(appSettings);

Some more usage examples are available in SlackLogFactoryTests.

Performance and Memory improvements

Several performance and memory usage improvements were also added across the board in this release where all ServiceStack libraries have now switched to using a ThreadStatic StringBuilder Cache where possible to reuse existing StringBuilder instances and save on Heap allocations.

For similar improvements you can also use the new StringBuilderCache in your own code where you’d just need to call Allocate() to get access to a reset StringBuilder instance and call ReturnAndFree() when you’re done to access the string and return the StringBuilder to the cache, e.g:

public static string ToMd5Hash(this Stream stream)
{
    var hash = MD5.Create().ComputeHash(stream);
    var sb = StringBuilderCache.Allocate();
    for (var i = 0; i < hash.Length; i++)
    {
        sb.Append(hash[i].ToString("x2"));
    }
    return StringBuilderCache.ReturnAndFree(sb);
}

There’s also a StringBuilderCacheAlt for when you need access to 2x StringBuilders at the same time.

String Parsing

We’ve switched to new APIs that have the same behavior as the existing SplitOnFirst() and SplitOnLast() extension methods but save allocating a temporary array:

str.LeftPart(':')      == str.SplitOnFirst(':')[0]
str.RightPart(':')     == str.SplitOnFirst(':').Last()
str.LastLeftPart(':')  == str.SplitOnLast(':')[0]
str.LastRightPart(':') == str.SplitOnLast(':').Last()

TypeConstants

We’ve switched to using the new TypeConstants which holds static instances of many popular empty collections and Task<T> results which you can reuse instead of creating new instances:

TypeConstants.EmptyStringArray == new string[0];
TypeConstants.EmptyObjectArray == new object[0];
TypeConstants<CustomType>.EmptyArray == new T[0];

CachedExpressionCompiler

We’ve added MVC’s CachedExpressionCompiler to ServiceStack.Common and where possible are now using it in-place of Compiling LINQ expressions directly in all of ServiceStack libraries.

Object Pools

We’ve added the Object pooling classes that Roslyn’s code-base uses in ServiceStack.Text.Pools which lets you create reusable object pools of instances. The available pools include:

  • ObjectPool<T>
  • PooledObject<T>
  • SharedPools
  • StringBuilderPool

Add ServiceStack Reference Wildcards

The IncludeType option in all Add ServiceStack Reference languages now allow specifying a .* wildcard suffix on Request DTO’s as a shorthand to return all dependent DTOs for that Service:

IncludeTypes: RequestDto.*

Special thanks to @donaldgray for contributing this feature.

New ServerEventsClient APIs

Use new Typed GetChannelSubscribers APIs added to C# ServerEventsClient to fetch Channel Subscribers:

var clientA = new ServerEventsClient("A");
var channelASubscribers = clientA.GetChannelSubscribers();
var channelASubscribers = await clientA.GetChannelSubscribersAsync();

RegisterServicesInAssembly

Plugins can use the new RegisterServicesInAssembly() API to register multiple Services in a specified assembly:

appHost.RegisterServicesInAssembly(GetType().Assembly);

This covers the main points, more features and further details are available in the full v4.0.58 Release Notes.

1 Like

This topic is now pinned globally. It will appear at the top of its category and all topic lists until it is unpinned by staff for everyone, or by individual users for themselves.

Hi Mythz - think there is a bug introduced in this version in regards to types metadata generation.
When I try to go to http://{api}/types/typescript.d or csharp or anything the service is returning an ArgumentException which I’ve tracked back to commit 2eb257f0864b77de8cb1e46f7ba663edb98b9faa which is accessing config.IncludeTypes without checking if its NULL.
As such all dto generation is not working.

Ahhh, that’s beyond brutal, have no idea how this got through. Should now be fixed with this commit which is now on MyGet.

Guess we’ll have to push out a rush v4.0.60 release on NuGet tomorrow as it’s too late to do one tonight. Thanks for bug report!

1 Like

We’re going to hold off deploying v4.0.60 until the end of the weekend to leave some time for anyone to report any other issues with v4.0.58 before we publish this minor release. So if you find any other issues please let us know soon and we’ll deal with them promptly!

@volak @ali FYI I’ve just published the latest v4.0.60 of ServiceStack to NuGet which contains this fix.

This topic is now unpinned. It will no longer appear at the top of its category.

This topic is now archived. It is frozen and cannot be changed in any way.

This topic is now unlisted. It will no longer be displayed in any topic lists. The only way to access this topic is via direct link.

This topic is now unarchived. It is no longer frozen, and can be changed.

This topic is now listed. It will be displayed in topic lists.