Authentication IdentityManager with IdentityServer in another domain

950 views Asked by At

After being authenticated by IdentityServer, IdentityManager issues an internal token like this. (Please run the example and check the bearer token sent after requesting for /api or api/Users)

authorization:Bearer UQgpIqqyn_lgUukES3PqHFEuf0_2sz26Jsh848K_4DYdiYeQLkSazg43MT2BdWSC-EY--iUYAPKk4rD9-8sq0_nbf2Z7XDzPlcDL0LdAP8oNyKUDCOLeap9zCEaB4ve1VE1Q_e5JGYsx_jTvs-yYlUI5fMn-6OBxunlNcTwPq-xv6hOXZhh-PUGIE9Ndhkptd0zt5r1A3UAvvTk72yI6yD40yRnl1KhNEQw33UNVMIeV4vWqwiXHtyoxi87e3r4_x3IyzZeEqxtwPIPH1l6o1s7HfZozspaTbaq9gPLvuaXa0dQjf5lA2CIGs5z8Fa3W

I actually need IdentityManager to save the JWT issued by my own IdentityServer during the login process and use that token to call the api instead of using the above kind of token. Why? Because I want to call an external API from IdentityManager itself which is expecting a Token issued by my own IdentityServer server.

Using HostSecurityConfiguration or LocalSecurityConfiguration is not working for me because they are using internally OAuthAuthorizationServerProvider and that provider issues a Token (the one above) that it's not valid for the API that eventually IdentityManager internally is going to call. The token saved MUST be issued by my own IdentityServer because the external API is expecting a token from it.

I tried using ExternalBearerTokenConfiguration without success. Each time I tried to do something with this class I am redirected to https://localhost:44337/idm/connect/authorize?state=8030030589322682&nonce=7778993666881238&client_id=idmgr&response_type=id_token%20token and this url obviously does not exist because the authority starts with https://localhost:44337/ids and the ExternalBearerTokenConfiguration is assuming that my provider is under the same domain.

This is the configuration for the ExternalBearerTokenConfiguration

idm.UseIdentityManager(new IdentityManagerOptions
            {
                Factory = factory,
                SecurityConfiguration = new ExternalBearerTokenConfiguration()
                {
                    RequireSsl = false,
                    SigningCert = Cert.Load(),
                    Issuer = "https://localhost:44337/ids",
                    Scope = "idmgr",
                    Audience = $"https://localhost:44337/ids/resources",
                    BearerAuthenticationType = "Cookies"
                }
            });

Going in another direction, I discovered that modifying the method GetResponseMessage() on IdentityManager.Assets.EmbeddedHtmlResult I can go to my IdentityServer and I ask for authentication which is really good. I can get my id_token and access token with everything inside as you can see. The good think about this approach is that the Token saved internally is the one I am getting from my IdentityServer.

{
    "client_id": "idmgr_client",
    "scope": [
    "openid",
    "idmgr",
    "WebUserAccountsApi"
    ],
    "sub": "951a965f-1f84-4360-90e4-3f6deac7b9bc",
    "amr": [
    "password"
    ],
    "auth_time": 1505323819,
    "idp": "idsrv",
    "name": "Admin",
    "role": "IdentityManagerAdministrator",
    "iss": "https://localhost:44336/ids",
    "aud": "https://localhost:44336/ids/resources",
    "exp": 1505327419,
    "nbf": 1505323819
}

So now I've got almost everything what I needed, when IdentityServer sends me back to my /idm endpoint (which is the endpoint for IdentityManager) UseIdentityServerBearerTokenAuthentication validates my token so I got authorized and I am sure of it because I can see everything in this line in the code below => context.Authentication.User.Identity.IsAuthenticated. The problem is that UseIdentityManager does not take my authorization even if UseIdentityServerBearerTokenAuthentication already did it.

When I remove the security in IdentityManager project and I put a breakpoint for example in api/Users I can see that the Principal has some values but all of them are empty. The claims are empty, the Identity itself has an object but is not Authenticated. Probably I am missing something in this code that is the glue between my UseIdentityServerBearerTokenAuthentication authentication and UseIdentityManager.

app.Map("/idm", idm =>
{
    var factory = new IdentityManagerServiceFactory();

    var rand = new System.Random();
    var users = Users.Get(rand.Next(5000, 20000));
    var roles = Roles.Get(rand.Next(15));

    factory.Register(new Registration<ICollection<InMemoryUser>>(users));
    factory.Register(new Registration<ICollection<InMemoryRole>>(roles));
    factory.IdentityManagerService = new Registration<IIdentityManagerService, InMemoryIdentityManagerService>();

    idm.Use(async (context, next) =>
    {
        await next.Invoke();
    });
    JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();

    idm.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
    {
        Authority = Constants.authority,
        RequiredScopes = new[] { "idmgr" }
    });

    idm.Use(async (context, next) =>
    {
        if (context.Authentication.User != null &&
                context.Authentication.User.Identity != null &&
                context.Authentication.User.Identity.IsAuthenticated)
        {
            /*var xxx = "";
        }
        await next.Invoke();
    });


    idm.UseIdentityManager(new IdentityManagerOptions
    {
        Factory = factory
    });

    idm.Use(async (context, next) =>
    {
        await next.Invoke();
    });
});

If you know what I am missing, please comment. All the ideas are welcomed. If you know how to get authenticated in IdentityManager using your own IdentityServer, please, tell me how to do it.

Thanks in advance. Daniel

2

There are 2 answers

0
Daniel Botero Correa On BEST ANSWER

I found a solution for this.

I create a class called OAuthSettings and another one called EmptySecurityConfiguration. I am using them like this:

idm.UseIdentityManager(new IdentityManagerOptions
            {
                Factory = factory,
                SecurityConfiguration = new EmptySecurityConfiguration
                {
                    OAuthSettings = new OAuthSettings()
                    {
                        authorization_endpoint = authority + "/connect/authorize",
                        client_id = "idmgr_client",
                        authority = authority,
                        response_type = "id_token token",
                        redirect_uri = idmUrl + "/#/callback/",
                        //scope = "openid",
                        scope = "openid idmgr MyApi",
                        //response_mode = ""
                        acr_values = "tenant:anything",
                        load_user_profile = true
                    }
                }
            });

I had to modify SecurityConfiguration class in order to add my OAuthSettings property.

Then I am using it like this inside of EmbeddedHtmlResult

OAuthSettings OAuthSettings = null;
        if (this.securityConfiguration.OAuthSettings == null)
        {
            OAuthSettings = new OAuthSettings
            {
                authorization_endpoint = this.authorization_endpoint,
                client_id = Constants.IdMgrClientId
            };
        }
        else
        {
            OAuthSettings = this.securityConfiguration.OAuthSettings;
        }

        var html = AssetManager.LoadResourceString(this.file,
            new {
                pathBase = this.path,
                model = Newtonsoft.Json.JsonConvert.SerializeObject(new
                {
                    PathBase = this.path,
                    ShowLoginButton = this.securityConfiguration.ShowLoginButton,
                    oauthSettings = OAuthSettings
                })
            });

After that you have to run the code and that's it. You've got the token issued by IdentityServer and saved it in order to be used in the javascript bit.

Now the Bearer Token looks like this:

authorization:Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJjbGllbnRfaWQiOiJpZG1ncl9jbGllbnQiLCJzY29wZSI6WyJvcGVuaWQiLCJpZG1nciIsIk15QXBpIl0sInN1YiI6Ijk1MWE5NjVmLTFmODQtNDM2MC05MGU0LTNmNmRlYWM3YjliYyIsImFtciI6WyJwYXNzd29yZCJdLCJhdXRoX3RpbWUiOjE1MDU1NzYzNTAsImlkcCI6Imlkc3J2IiwibmFtZSI6IkFkbWluIiwicm9sZSI6IklkZW50aXR5TWFuYWdlckFkbWluaXN0cmF0b3IiLCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzOC9pZHMiLCJhdWQiOiJodHRwczovL2xvY2FsaG9zdDo0NDMzOC9pZHMvcmVzb3VyY2VzIiwiZXhwIjoxNTA1NTgwMzU4LCJuYmYiOjE1MDU1NzY3NTh9.iVsEuswGDdMGo-x-NdPxMEln6or9e7p8G-8iSK746_Wapcwi_-N7EcY3G8GKj0YvExO4i605kfNjsTDAd14zQvT6UyU8_gcGO84DhQRM_MWpirfhlPWu6flXT4dRzYberjgHhDEOzROsrHofVAAZD_51BEE1FgAQrqCCWar2POSi9AsLFJ_AxFRnMlbZbZy8adJiMGOUFhtBXzhJVYzuolAMJ08NBTzmaK5vLsEn9Ok-09ZGX3MOpq2aBfES1hRJKEP-LDhMNo4dQn0mQ9Y-gGvkpXMmZQ6tC8yUs2PokJ5eGsFqevK6zpvJDiKPPjoN01QJtEqZ2UU_oGzMEKwyUA

I created a github repository with the code

3
Rob Davis On

The IdentityManager repo has an example of using IdentityServer3 as an Idp for IdentityManager.

Some relevant discussion can be found in this thread as well...

Edit:

I haven't studied what IdentityManager is internally doing with tokens as you described. However, for your external API call, couldn't you also request an access token (instead of only an id_token), then save that access token and use it to make the calls to your external API? This would be a token issued by Identity Server and it would be a JWT by default.

Here's how the code from the example would change; please see the code under the two comments marked: "--EDIT-- ..."

Essentially, I am just requesting and Access Token and then saving it....

app.UseOpenIdConnectAuthentication(new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationOptions
{
    AuthenticationType = "oidc",
    Authority = "https://localhost:44337/ids",
    ClientId = "idmgr_client",
    RedirectUri = "https://localhost:44337",

    // ---EDIT--- request id_token AND access_token
    ResponseType = "id_token token",

    UseTokenLifetime = false,
    Scope = "openid idmgr",
    SignInAsAuthenticationType = "Cookies",
    Notifications = new Microsoft.Owin.Security.OpenIdConnect.OpenIdConnectAuthenticationNotifications
    {
        SecurityTokenValidated = n =>
        {
            n.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));

            // --EDIT-- save access_token
            n.AuthenticationTicket.Identity.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));

            return Task.FromResult(0);
        },
        RedirectToIdentityProvider = async n =>
        {
            if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnectRequestType.LogoutRequest)
            {
                var result = await n.OwinContext.Authentication.AuthenticateAsync("Cookies");
                if (result != null)
                {
                    var id_token = result.Identity.Claims.GetValue("id_token");
                    if (id_token != null)
                    {
                        n.ProtocolMessage.IdTokenHint = id_token;
                        n.ProtocolMessage.PostLogoutRedirectUri = "https://localhost:44337/idm";
                    }
                }
            }
        }
    }
});