New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple authentication schemes and global filters (ASP.NET Core 2) #1559

Closed
johnkors opened this Issue Nov 30, 2017 · 12 comments

Comments

Projects
None yet
7 participants
@johnkors

johnkors commented Nov 30, 2017

Hi,

I'm struggling to get the following auth working in an OK manner, which of I find a quite common use case. I want to co-host a API and a UI within the same ASP.NET Core MVC 2 host, but keeping the authorization setup separate.

  • /api
    Every route starting with /apishould have JWT bearer auth requiring the tokens to contain a specific scope.

  • /anyotherurl :
    Any other URI should use cookies and openid connect, and use a global filter forcing authenticated users, so I don´t need to add an [Authorize] attribute on every new UI controller added to the project now or later.

Issue I'm facing
When I add global filters for cookies, they are applied to my API bits as well. This is undesirable.

            services.AddAuthorization(o =>
            {
     
                var authorizationPolicy = new AuthorizationPolicyBuilder(authenticationSchemes: OpenIdConnectDefaults.AuthenticationScheme)
                    .RequireAuthenticatedUser()
                    .Build();

                o.AddPolicy("RequireLoggedOnUsers", authorizationPolicy);
            });

          services.AddMvc(
                o =>
                {
                    o.Filters.Add(new AuthorizeFilter("RequireLoggedOnUsers"));
                });

I've tried using IActionModelConventions , but it does not seem to do anything to globally applied filters (e.g if I add a global filter for OIDC, I cannot "remove" it from the API routes filters).

Is there any other good way of seperating auth (APIs / UI) whilst also keeping the good parts of having global auth policies applied ..?

A [DisableGlobalFiltersFor("/api")], or a convention that allows for rejecting global filters?

@Eilon

This comment has been minimized.

Show comment
Hide comment
@Eilon

Eilon Nov 30, 2017

Member

Hi @johnkors , this is indeed an area that is not very easy to work with in 2.0. We are working on some improvements that should make this better in the 2.1 milestone, which you can follow via this issue: #1546

Member

Eilon commented Nov 30, 2017

Hi @johnkors , this is indeed an area that is not very easy to work with in 2.0. We are working on some improvements that should make this better in the 2.1 milestone, which you can follow via this issue: #1546

@Eilon Eilon closed this Nov 30, 2017

@johnkors

This comment has been minimized.

Show comment
Hide comment
@johnkors

johnkors Dec 1, 2017

Not sure I follow how #1546 handles this scenario. #1546 looks like just an helper overload for doing cookies+social. That's not what I'm concerned about here.

johnkors commented Dec 1, 2017

Not sure I follow how #1546 handles this scenario. #1546 looks like just an helper overload for doing cookies+social. That's not what I'm concerned about here.

@johnkors

This comment has been minimized.

Show comment
Hide comment
@johnkors

johnkors Dec 1, 2017

Is it not possible to create something that makes a policy only applicable for provided set of routes? In the Katana world, one could at least just use middleware to accomplish seperation.

app.Map(IsApiRequest()), a => a.UseBearer());
app.Map(!IsApiRequest()); a => a.UseCookiesAndOpenId());

johnkors commented Dec 1, 2017

Is it not possible to create something that makes a policy only applicable for provided set of routes? In the Katana world, one could at least just use middleware to accomplish seperation.

app.Map(IsApiRequest()), a => a.UseBearer());
app.Map(!IsApiRequest()); a => a.UseCookiesAndOpenId());
@Tratcher

This comment has been minimized.

Show comment
Hide comment
@Tratcher

Tratcher Dec 3, 2017

Member

See #1546 (comment). Map almost worked with Core 1.x, but it won't work at all with the 2.0 design.

Member

Tratcher commented Dec 3, 2017

See #1546 (comment). Map almost worked with Core 1.x, but it won't work at all with the 2.0 design.

@johnkors

This comment has been minimized.

Show comment
Hide comment
@johnkors

johnkors Dec 14, 2017

Right, thanks. I ended up with something working setting it up like this.

services.AddAuthentication()
    .AddVirtualScheme(VirtualAuthDAuthenticationScheme, VirtualAuthDAuthenticationScheme, o =>
    {
        o.DefaultSelector = c =>
        {           
            if (c.Request.Path.StartsWithSegments("/api"))
            {
                return JwtBearerDefaults.AuthenticationScheme;
            }
            return OpenIdConnectDefaults.AuthenticationScheme;
        };
    })
    .AddCookie()
    .AddOpenIdConnect()
    .AddJwtBearer();
    });

services.AddAuthorization(o =>
{
    var authorizationPolicy = new AuthorizationPolicyBuilder(VirtualAuthDAuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();
    o.AddPolicy("RequireLoggedOnUsers", authorizationPolicy);
});

services.AddMvc(o =>
{
    o.Filters.Add(new AuthorizeFilter("RequireLoggedOnUsers"));
});

johnkors commented Dec 14, 2017

Right, thanks. I ended up with something working setting it up like this.

services.AddAuthentication()
    .AddVirtualScheme(VirtualAuthDAuthenticationScheme, VirtualAuthDAuthenticationScheme, o =>
    {
        o.DefaultSelector = c =>
        {           
            if (c.Request.Path.StartsWithSegments("/api"))
            {
                return JwtBearerDefaults.AuthenticationScheme;
            }
            return OpenIdConnectDefaults.AuthenticationScheme;
        };
    })
    .AddCookie()
    .AddOpenIdConnect()
    .AddJwtBearer();
    });

services.AddAuthorization(o =>
{
    var authorizationPolicy = new AuthorizationPolicyBuilder(VirtualAuthDAuthenticationScheme)
        .RequireAuthenticatedUser()
        .Build();
    o.AddPolicy("RequireLoggedOnUsers", authorizationPolicy);
});

services.AddMvc(o =>
{
    o.Filters.Add(new AuthorizeFilter("RequireLoggedOnUsers"));
});
@akashdeep1024

This comment has been minimized.

Show comment
Hide comment
@akashdeep1024

akashdeep1024 Feb 1, 2018

@johnkors do you have git hub sample solution of Multiple Scheme and global filter ?

akashdeep1024 commented Feb 1, 2018

@johnkors do you have git hub sample solution of Multiple Scheme and global filter ?

@johnkors

This comment has been minimized.

Show comment
Hide comment
@johnkors

johnkors Feb 2, 2018

@akashdeep1024 : In the comment above, there's a global filter RequireLoggedOnUsers and 3/4 schemes.

johnkors commented Feb 2, 2018

@akashdeep1024 : In the comment above, there's a global filter RequireLoggedOnUsers and 3/4 schemes.

@klick-barakgall

This comment has been minimized.

Show comment
Hide comment
@klick-barakgall

klick-barakgall May 7, 2018

@johnkors Sorry to revive this, but I can't find any reference to AddVirtualScheme anywhere offline or online except for some patch notes regarding APIs that are being made public. Is this something that might have been deprecated? Is this still working for you?

EDIT: Nevermind, my apologies. I see now it's a class available in the referenced issue.

klick-barakgall commented May 7, 2018

@johnkors Sorry to revive this, but I can't find any reference to AddVirtualScheme anywhere offline or online except for some patch notes regarding APIs that are being made public. Is this something that might have been deprecated? Is this still working for you?

EDIT: Nevermind, my apologies. I see now it's a class available in the referenced issue.

@HaoK

This comment has been minimized.

Show comment
Hide comment
@HaoK

HaoK May 8, 2018

Member

We did tweak things a bit starting in preview2, we enabled the forwarding in all schemes, so you can forward in all existing schemes (i.e. cookies, OAuth, google), AddVirtualScheme was renamed AddPolicyScheme for the scenario where you want a pure virtual scheme that isn't attached to any specific auth handler logic.

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddCookie()
    .AddOpenIdConnect(o.FowardDefaultSelector = c =>
        {           
            if (c.Request.Path.StartsWithSegments("/api"))
            {
                return JwtBearerDefaults.AuthenticationScheme;
            }
            return null; // Don't change the default, could also just return the OIDC default as well
        };
)
    .AddJwtBearer();
    });
Member

HaoK commented May 8, 2018

We did tweak things a bit starting in preview2, we enabled the forwarding in all schemes, so you can forward in all existing schemes (i.e. cookies, OAuth, google), AddVirtualScheme was renamed AddPolicyScheme for the scenario where you want a pure virtual scheme that isn't attached to any specific auth handler logic.

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddCookie()
    .AddOpenIdConnect(o.FowardDefaultSelector = c =>
        {           
            if (c.Request.Path.StartsWithSegments("/api"))
            {
                return JwtBearerDefaults.AuthenticationScheme;
            }
            return null; // Don't change the default, could also just return the OIDC default as well
        };
)
    .AddJwtBearer();
    });
@klick-barakgall

This comment has been minimized.

Show comment
Hide comment
@klick-barakgall

klick-barakgall May 8, 2018

@HaoK Sounds amazing, can't wait to hack around with that. But I'm working on some production-bound code and need to figure out how to get something done now.

Essentially I am using .AddCookie().AddGoogle() on my authenticationbuilder, but when that comes back with a user I need to A) check that the email ends with a specific domain and B) ask the user to enter a user-specific password or PIN. I've been banging my head against the wall for a while now, and can use any assistance that might be available.

klick-barakgall commented May 8, 2018

@HaoK Sounds amazing, can't wait to hack around with that. But I'm working on some production-bound code and need to figure out how to get something done now.

Essentially I am using .AddCookie().AddGoogle() on my authenticationbuilder, but when that comes back with a user I need to A) check that the email ends with a specific domain and B) ask the user to enter a user-specific password or PIN. I've been banging my head against the wall for a while now, and can use any assistance that might be available.

@HaoK

This comment has been minimized.

Show comment
Hide comment
@HaoK

HaoK May 8, 2018

Member

So you are basically doing two factor auth with Google + PIN/local password? You will just need to build that flow yourself, it shouldn't look that different than what the templates do today, if you didn't want to use identity to do the password verification you would just replaces those calls to your own password verification logic

Member

HaoK commented May 8, 2018

So you are basically doing two factor auth with Google + PIN/local password? You will just need to build that flow yourself, it shouldn't look that different than what the templates do today, if you didn't want to use identity to do the password verification you would just replaces those calls to your own password verification logic

@hempels

This comment has been minimized.

Show comment
Hide comment
@hempels

hempels May 18, 2018

@HaoK, I have a different scenario for combining authentication methods that I'm wondering if it can be handled via VirtualSchemes or (even better) can be achieved without them? Due to the nature of our app (education software) we can have users coming in via a wide number of auth providers, many of them using OAuth 1.0 (specifically, LTI). Rather than always creating a new user account whenever we don't recognize a login and then having to deal with complex identity merges later, we want to invite 'new' users to identify themselves via OpenID (Google and Microsoft primarily since that covers most schools.) We could ask them for their U/P, except we don't do U/P - we have always preferred to only support login via 3rd party identity providers and don't really want to change that.

So the scenario is that one authentication scheme (LTI/OAuth1.0) receives the user's identity info, recognizes that this identity is new to our system, and then forwards a challenge to our default scheme. Upon completion of that scheme (either successful auth or declining (i.e. NoResult)) we would ideally return to the original scheme to complete either creating a new user using the provided claims or adding an additional login to the existing user. Once all of that was complete, the final AuthenticationTicket would be returned and the request would process normally.

https://stackoverflow.com/questions/50421604/

hempels commented May 18, 2018

@HaoK, I have a different scenario for combining authentication methods that I'm wondering if it can be handled via VirtualSchemes or (even better) can be achieved without them? Due to the nature of our app (education software) we can have users coming in via a wide number of auth providers, many of them using OAuth 1.0 (specifically, LTI). Rather than always creating a new user account whenever we don't recognize a login and then having to deal with complex identity merges later, we want to invite 'new' users to identify themselves via OpenID (Google and Microsoft primarily since that covers most schools.) We could ask them for their U/P, except we don't do U/P - we have always preferred to only support login via 3rd party identity providers and don't really want to change that.

So the scenario is that one authentication scheme (LTI/OAuth1.0) receives the user's identity info, recognizes that this identity is new to our system, and then forwards a challenge to our default scheme. Upon completion of that scheme (either successful auth or declining (i.e. NoResult)) we would ideally return to the original scheme to complete either creating a new user using the provided claims or adding an additional login to the existing user. Once all of that was complete, the final AuthenticationTicket would be returned and the request would process normally.

https://stackoverflow.com/questions/50421604/

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