Update 5/12/2016: We’re thrilled to announce our open-source ASP.NET Core authentication library is now available! Learn how to build an ASP.NET Core application with user authentication.
Exciting things are happening in .NET Land! The Stormpath .NET SDK is quickly marching towards a full, feature-rich ASP.NET integration. Big thanks to all the folks who have reached out over email and on Github to let us know what features you are looking for in this library!
In this release, we’ve fixed some bugs and added two crucial pieces: ID Site for single sign-on support and a client-side caching layer. As usual, there are a few other goodies thrown in too!
Getting the Newest .NET SDK Release
As always, installing the SDK is as easy as:
install-package Stormpath.SDK
If you’re already using the SDK, this release should be a drop-in upgrade:
update-package Stormpath.SDK
For a full list of changes, see the changelog on Github.
Easy Single Sign-On with ID Site for .NET
We’ve added full support for Stormpath’s ID Site single sign-on solution in this release. With this feature, you’ll be able to enable a hosted login flow for your application in only a few lines of code.
Using ID Site from your web application is a two-step process:
- Generate an ID Site redirect URL
- Handle the ID Site result callback
Generate an ID Site Redirect URL
This part’s easy. Simply use the IIdSiteUrlBuilder
interface to construct the appropriate URL, and redirect the user:
1 2 3 4 5 |
var redirectUrl = application.NewIdSiteUrlBuilder() .SetCallbackUri("https://my-site-url/HandleRedirect") .Build(); // Redirect the user to redirectUrl |
Handle the ID Site Result Callback
After ID Site has handled the login request, the user will be redirected back to your callback address (https://my-site-url/HandleRedirect
in this example).
In the controller that handles the HandleRedirect
endpoint, create and call an ID Site handler to process the callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Construct an IHttpRequest wrapper from the current Request object var requestDescriptor = HttpRequests .NewRequestDescriptor() .WithMethod(Request.HttpMethod) .WithUri(Request.Url) .Build(); // Construct an ID Site callback handler var handler = application .NewIdSiteAsyncCallbackHandler(requestDescriptor); // Get the result of the ID Site request var accountResult = await handler.GetAccountResultAsync(); // accountResult.Status is one of: // IdSiteResultStatus.Authenticated // IdSiteResultStatus.Logout // IdSiteResultStatus.Registered if (accountResult.Status != IdSiteResultStatus.Logout) { var account = await accountResult.GetAccountAsync(); // Do cool things with account } |
For more information on how to build applications that use ID Site, consult the ID Site documentation.
Faster Performance with Client-Side Caching
Another important feature added in this release is client-side caching support. This can reduce the number of requests made to the Stormpath API and improve the performance of your application.
The caching layer intelligently intercepts requests made to Stormpath and stores the resource data locally. If the same data needs to be retrieved again, the data is pulled from the local cache instead of making a network request.
The caching layer is designed to be pluggable through the ICacheProvider
interface. If you don’t want to use the default cache implementation, you can swap another implementation out easily.
Out of the box, the .NET SDK ships with an in-memory cache that’s suitable for use on a single-server application. For applications that run on multiple servers, a distributed cache is highly recommended. This is covered in the next section.
Starting with this release, caching is enabled by default. If your application runs on a single-server instance, you don’t have to do anything! The cache layer is already working for you.
Disabling Client-Side Caching
If you’d rather not cache requests, you can specify this when building an IClient
object:
1 2 3 4 5 |
var client = Clients.Builder() // (other configuration) .SetCacheProvider(CacheProviders.Create().DisabledCache()) .Build(); |
Customizing Cache TTL and TTI
Unless otherwise specified, the cache time-to-live (TTL) and time-to-idle (TTI) default to one hour. These defaults can be changed when building a new ICacheProvider
instance.
For example, to set a TTL of 2 hours and a TTI of 30 minutes:
1 2 3 4 5 6 7 8 9 10 |
var myCacheProvider = CacheProviders.Create().InMemoryCache() .WithDefaultTimeToLive(TimeSpan.FromHours(2)) .WithDefaultTimeToIdle(TimeSpan.FromMinutes(30)) .Build(); var client = Clients.Builder() // (other configuration) .SetCacheProvider(myCacheProvider) .Build(); |
These values can be further customized by cache region. The .NET SDK treats each resource type as a separate cache region, so per-resource type TTL and TTI values can be set by specifying a resource type while building the ICacheProvider
:
1 2 3 4 5 6 7 8 9 |
var myCacheProvider = CacheProviders.Create().InMemoryCache() .WithDefaultTimeToLive(TimeSpan.FromHours(2)) .WithDefaultTimeToIdle(TimeSpan.FromMinutes(30)) .WithCache(Caches .ForResource<IAccount>() .WithTimeToLive(TimeSpan.FromMinutes(15)) .WithTimeToIdle(TimeSpan.FromMinutes(5))) .Build(); |
In this case, every cached resource will have the default TTL (2 hours) and TTI (30 minutes), except for Account resources, which expire after 15 minutes, or 5 minutes of inactivity.
Distributed Caching with Redis
The default in-memory cache is not suitable for applications that run on multiple servers (such as a load-balancing configuration), because each server maintaining its own local cache can lead to coherence problems. In this case, a distributed caching technology such as Redis or Memcached is highly recommended.
If you’re using Redis, we’ve got you covered. A Redis cache adapter is available in the nuget package Stormpath.SDK.Cache.Redis
. Plugging it in is easy:
1 2 3 4 5 6 7 8 9 10 11 |
var cacheProvider = CacheProviders.Create().RedisCache() .WithRedisConnection("server:6379") .WithDefaultTimeToLive(TimeSpan.FromMinutes(60)) .WithDefaultTimeToIdle(TimeSpan.FromMinutes(30)) .Build(); var client = Clients.Builder() // (other configuration) .SetCacheProvider(cacheProvider) .Build(); |
If your project uses a different distributed cache, the source code for this implementation should provide a starting point to writing a custom ICacheProvider
. If you need help getting a specific caching technology working, drop us a line.
Further Performance Improvements with Link Expansion
The Stormpath REST API supports link expansion as a way to retrieve multiple related resources in a single request. This works beautifully as a way to prime the cache with data you know you’ll need in the future.
In this release, we’ve added a new Expand
operator that exposes this API behavior. You can use this operator when making a single-resource request, or in the context of a LINQ search query.
For example, when retrieving an Account, you could also retrieve and cache the account’s custom data:
1 2 3 4 5 6 7 |
var account = await client.GetResourceAsync<IAccount>( accountHref, opt => opt.Expand(x => x.GetCustomData())); // If caching is enabled, no HTTP request is made here: var accountCustomData = await account.GetCustomDataAsync(); |
You can expand multiple properties, too. For example, when performing an account search, you could retrieve each account’s custom data and directory information:
1 2 3 4 5 6 7 |
var accounts = await client .GetAccounts() .Where(x => x.Surname.StartsWith("Sky")) .Expand(x => x.GetCustomData()) .Expand(x => x.GetDirectory()) .ToListAsync(); |
The additional data is silently passed to the cache, and the resulting type of your query won’t change. It’s all under the hood.
With strategic use of Expand
, you can prime the cache with data you know you’ll need, and cut down even more on network traffic compared to naive caching alone.
New Account Store API for Directories and Groups
This release also adds some additional methods and interfaces for managing Account Stores (Directories and Groups) mapped to Applications. For background on how Stormpath uses Account Stores, see the Account Store Mapping documentation.
Adding and Removing Account Stores From a .NET Application
Adding Directories and Groups to an Application as an Account Store Mapping requires only one line of code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Add by name: await app.AddAccountStoreAsync("My Group Or Directory Name"); // Or, add by href: await app.AddAccountStoreAsync(hrefOfGroupOrDirectory); // Or, from an existing IDirectory or IGroup object: await app.AddAccountStoreAsync(myGroupOrDirectory); // If you want to get really fancy, you can add by the // result of a query (provided the query produces one item only): await app.AddAccountStoreAsync<IDirectory>( dirs => dirs.Where(d => d.Name.StartsWith("My Dir"))); |
Controlling Login Order With Account Stores
The listIndex
property of an Account Store Mapping determines when it is consulted in the Stormpath login flow. Account Store Mappings with higher priority are consulted first. By default, newly-created mappings are given lowest priority.
You can customize this by setting the Account Store Mapping properties directly:
1 2 3 4 5 |
var mapping = client.Instantiate<IApplicationAccountStoreMapping>(); mapping.SetAccountStore(existingGroupOrDirectory); mapping.SetListIndex(0); await app.CreateAccountStoreMappingAsync(mapping); |
Setting Default Account and Group Stores
Applications cannot store Accounts or Groups directly; Accounts and Groups are always stored in an Account Store (a Directory or Group). You can specify which Account Stores are the default Account and Group store by setting these properties when creating a mapping:
1 2 3 4 5 6 |
var mapping = client.Instantiate<IApplicationAccountStoreMapping>(); mapping.SetAccountStore(existingGroupOrDirectory); mapping.SetDefaultAccountStore(true); mapping.SetDefaultGroupStore(true); await app.CreateAccountStoreMappingAsync(mapping); |
Alternatively, you can set the default account or group store directly from the application itself:
1 2 |
await app.SetDefaultAccountStoreAsync(myGroupOrDirectory); |
In this example, if an Account Store Mapping already exists, it is updated and set as the default Account Store. If it does not exist, it is created.
Specifying an Account Store During Requests
In addition, you can specify an Account Store, Account Store href
, or Organization nameKey
during login and password reset requests. In Applications that have a large number of associated Account Stores, this can reduce the time it takes to process a request.
1 2 3 4 5 6 7 8 9 10 11 |
// Specify an Account Store to search during a login attempt var loginResult = await app.AuthenticateAccountAsync(request => request .SetUsernameOrEmail("sonofthesuns") .SetPassword("whataPieceofjunk$1138") .SetAccountStore(accountStore)); // Send a password reset email to the account // in the specified Account Store await app.SendPasswordResetEmailAsync( |
What’s Next for the .NET SDK?
The next few releases will be super exciting. Here’s what’s on the roadmap:
Is there something specific you want to see? Join the conversation on Github or shoot us an email.
Happy coding!