IdentityServer / IdentityServer4

OpenID Connect and OAuth 2.0 Framework for ASP.NET Core
https://identityserver.io
Apache License 2.0
9.23k stars 4.02k forks source link

State on URL is too long for Azure AD #407

Closed rposener closed 8 years ago

rposener commented 8 years ago

When using Azure AD as a federated provider, the state querystring is too long to return with the claims. It does seem to be an issue more on Azure's side, but is there an easy way to make the state shorter? this is the config I'm using in Startup.cs on the IdentityServer in QuickStart4_ExternalAuthentication. Google works fine, but adding this for Azure AD fails:

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions { DisplayName = "Azure AD", SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme, SignOutScheme = IdentityServerConstants.SignoutScheme, ClientId = "XXXXXXXXXXXXXXXXXXXXXXXXXXX", ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXX", Authority = string.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}", "common", "/v2.0"), ResponseType = OpenIdConnectResponseType.IdToken, PostLogoutRedirectUri = "https://localhost:44326/", Events = new OpenIdConnectEvents { OnRemoteFailure = OnAuthenticationFailed, } });

the same config works fine in a plain MVC app. Also, if I remove the state from the URL, Azure AD finishes the auth and redirects me back without issue. But of course IdentityServer can't process it without a state parameter.

rposener commented 8 years ago

For anyone else finding this issue, I found the resolution from another user's fork of the repository. Basically, you need to cache the state, and provide a different State Format.

In Startup.cs do this: ` var dataProtectionProvider = app.ApplicationServices.GetRequiredService(); var distributedCache = app.ApplicationServices.GetRequiredService();

        var dataProtector = dataProtectionProvider.CreateProtector(
            typeof(OpenIdConnectMiddleware).FullName,
            typeof(string).FullName, schemeName,
            "v1");

        var dataFormat = new CachedPropertiesDataFormat(distributedCache, dataProtector);`

then in OpenIdConnectOptions set the property StateDataFormat = dataFormat

here is the code for the CachedPropertiesDataFormat class `using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using IdentityServer4.Services; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http.Authentication; using Microsoft.Extensions.Caching.Distributed;

namespace QuickstartIdentityServer.Authentication { ///

/// Custom secure data format intended for use with OpenIdConnectMiddleware.
/// It is intended to reduce the size of the state parameter generated
/// by the default PropertiesDataFormat, which results in query strings
/// that are too long for Azure AD to handle.
/// </summary>

public class CachedPropertiesDataFormat
    : ISecureDataFormat<AuthenticationProperties>
{
    public const string CacheKeyPrefix = "CachedPropertiesData-";

    private readonly IDistributedCache _cache;
    private readonly IDataProtector _dataProtector;
    private readonly IDataSerializer<AuthenticationProperties> _serializer;

    public CachedPropertiesDataFormat(
        IDistributedCache cache,
        IDataProtector dataProtector)
        : this(cache, dataProtector, new PropertiesSerializer())
    {

    }

    public CachedPropertiesDataFormat(
        IDistributedCache cache,
        IDataProtector dataProtector,
        IDataSerializer<AuthenticationProperties> serializer)
    {
        _dataProtector = dataProtector;
        _cache = cache;
        _serializer = serializer;
    }

    public string Protect(AuthenticationProperties data)
    {
        return Protect(data, null);
    }

    public string Protect(AuthenticationProperties data, string purpose)
    {
        var key = Guid.NewGuid().ToString();
        var cacheKey = $"{CacheKeyPrefix}{key}";
        var serialized = _serializer.Serialize(data);

        // Rather than encrypt the full AuthenticationProperties
        // cache the data and encrypt the key that points to the data
        _cache.Set(cacheKey, serialized);

        return _dataProtector.Protect(key);
    }

    public AuthenticationProperties Unprotect(string protectedText)
    {
        return Unprotect(protectedText, null);
    }

    public AuthenticationProperties Unprotect(string protectedText, string purpose)
    {
        // Decrypt the key and retrieve the data from the cache.
        var key = _dataProtector.Unprotect(protectedText);
        var cacheKey = $"{CacheKeyPrefix}{key}";
        var serialized = _cache.Get(cacheKey);

        return _serializer.Deserialize(serialized);
    }

}

}`

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.