Skip to content
This repository has been archived by the owner on Sep 18, 2021. It is now read-only.

Is it possible to use tokens for API and cookies for MVC in the same app? #487

Closed
B3nCr opened this issue Oct 28, 2014 · 35 comments
Closed
Assignees
Labels

Comments

@B3nCr
Copy link

B3nCr commented Oct 28, 2014

The question is all in the title really.

I've got a single page app that I'd like to serve using MVC. It calls an API that I'd like to include in the same application so that we don't have to deal with CORS. It seems an un-necessary extra HTTP request to call OPTIONS when I can host the API in the same MVC site.

Is it possible to have API controllers and MVC controllers in the same project and have the API controllers authenticated using a token and MVC controllers authenticated using a cookie?

I've noticed the following in the WebApiConfig class of a new WebAPI project.

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

This method isn't available in my combination project, presumably because it's got a later version of OWIN.OAuth package.

<package id="Microsoft.Owin.Security.OAuth" version="2.1.0" targetFramework="net45" />
vs
<package id="Microsoft.Owin.Security.OAuth" version="3.0.0" targetFramework="net45" />
@brockallen
Copy link
Member

Yes, it's possible. You do need to stitch together the proper configuration. Look at some of the samples for this if you're stuck.

@B3nCr
Copy link
Author

B3nCr commented Oct 28, 2014

Haha, thanks @brockallen. Any more clues than that?

Presumably you're being abrupt because I've missed something obvious on some documentation somewhere.

@brockallen
Copy link
Member

Actually, the SPA template also isn't a bad sample to start with, except they're using the embedded OAUth2 authorization server -- so you'd just rip that out and use external bearer tokens instead (as per our samples).

Sample repo is here: https://github.com/thinktecture/Thinktecture.IdentityServer.v3.Samples

@B3nCr
Copy link
Author

B3nCr commented Oct 28, 2014

I'm still struggling with this. Using the following config works to a point, I can authenticate with the API using just a token, I can authenticate with the MVC controllers using a cookie.

However I can still authenticate against the API with the cookie and I don't want to be able to do that, it means I have to handle XSRF somehow, I'd rather just use a token for API access.

If I call SuppressDefaultHostAuthentication on the API configuration it breaks all authentication for the API.

public void Configuration(IAppBuilder app)
{
    var identityServerUri = ConfigurationManager.AppSettings[IdentityServerUrlKey];
    var redirectUri = ConfigurationManager.AppSettings[RedirectUriKey];

    identityServerUri = "https://b3ncr.auth:44340/identity";
    redirectUri = "https://b3ncr.comms:44341/";

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = "Cookies"
    });
    // Enable the application to use a cookie to store information for the signed in user
    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        Authority = identityServerUri,
        ClientId = ClientId,
        RedirectUri = redirectUri,
        Scope = "openid profile sampleApi",
        SignInAsAuthenticationType = "Cookies"
    });

    app.UseIdentityServerJwt(new JwtTokenValidationOptions
    {
        Authority = "https://b3ncr.auth:44340/identity"
    });

    app.Map("/api", inner =>
    {
        // Web API configuration and services
        var config = new HttpConfiguration();

        /*
        * Including this causes the request not to be authorised even when a token is present.
        */
        //config.SuppressDefaultHostAuthentication();

        // Web API routes
        //config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        inner.UseWebApi(config);

        inner.RequireScopes(new ScopeValidationOptions() { Scopes = new[] { "sampleApi" }, AllowAnonymousAccess = true });
    });
}

@brockallen
Copy link
Member

In the /api Map in the HttpConfiguration set a DefaultHostAuthentication filter for "Bearer". In addition to using SuppressDefaultHostAuthentication.

@brockallen brockallen self-assigned this Oct 28, 2014
@B3nCr
Copy link
Author

B3nCr commented Oct 28, 2014

I've added config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); which I've taken from the single page app template but the request is still not authorised.

Should the following line be done inside the /api map or outside like in my example above?

app.UseIdentityServerJwt(new JwtTokenValidationOptions
{
    Authority = "https://b3ncr.auth:44340/identity"
});

@brockallen
Copy link
Member

UseIdentityServerJwt is meant for your API, so inside the map for the API.

@B3nCr
Copy link
Author

B3nCr commented Oct 28, 2014

I've tried it inside the map and outside the map. Still 401. A CORS request to a different endpoint (which is setup as token only) using the same token works so the token is valid.

@B3nCr
Copy link
Author

B3nCr commented Oct 28, 2014

Removing the Authorize attribute from the endpoint allows me to call it so it's not a routing issue.

@B3nCr
Copy link
Author

B3nCr commented Oct 28, 2014

I found this helpful guide which explains why I'm removing the default authentication and adding my own. http://brockallen.com/2013/10/27/host-authentication-and-web-api-with-owin-and-active-vs-passive-authentication-middleware/

@leastprivilege
Copy link
Member

The access token validation MW did not use the Bearer authentication type - that has changed in beta 3 - that might be the issue you are seeing.

@B3nCr
Copy link
Author

B3nCr commented Oct 29, 2014

I was using slightly different versions of some Nuget packages but everything is consistent now and I'm still getting the same issue.

So what authentication type am I supposed to use if not Bearer. Is HostAuthenticationFilter the correct filter or is there a Thinktecture version I should be using.

Presumably the access token is just an OAuth token once the OpenID steps are completed.

@B3nCr
Copy link
Author

B3nCr commented Oct 29, 2014

In my map I have the following.

app.Map("/api", inner =>
{
    // Web API configuration and services
    var config = new HttpConfiguration();

    config.SuppressDefaultHostAuthentication();
    config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

    inner.UseIdentityServerJwt(new JwtTokenValidationOptions
    {
        Authority = "https://b3ncr.auth:44340/identity"

    });
    inner.RequireScopes(new ScopeValidationOptions() { Scopes = new[] { "sampleApi" } });

    // Web API routes
    //config.MapHttpAttributeRoutes();

    config.Routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

    inner.UseWebApi(config);
});

If I include the following lines the API call returns a 403 now instead of a 401. Does this point to some other configuration error?

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

My nuget package versions are as follows...

<package id="Microsoft.AspNet.WebApi" version="5.2.2" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.2" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.2" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Owin" version="5.2.2" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.2" targetFramework="net45" />
<package id="Microsoft.Owin.Host.SystemWeb" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Security.Jwt" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Security.OAuth" version="3.0.0" targetFramework="net45" />
<package id="Microsoft.Owin.Security.OpenIdConnect" version="3.0.0" targetFramework="net45" />
<package id="Thinktecture.IdentityModel.Owin.ScopeValidation" version="2.0.0" targetFramework="net45" />
<package id="Thinktecture.IdentityServer.v3.AccessTokenValidation" version="1.0.0-beta2" targetFramework="net45" />

I think I've only included the appropriate ones.

@leastprivilege
Copy link
Member

does the 403 have a "insufficient_scope" error message?

@B3nCr
Copy link
Author

B3nCr commented Oct 29, 2014

Ahh, it does yes.

@B3nCr
Copy link
Author

B3nCr commented Oct 29, 2014

The token has the scope which is defined in this though because the same scope is required in my CORS API which works correctly.

inner.RequireScopes(new ScopeValidationOptions() { Scopes = new[] { "sampleApi" } });

@B3nCr
Copy link
Author

B3nCr commented Oct 29, 2014

This is fun. I'm getting a 401 again.

I think I made a mistake earlier, I seem get the 403 when I don't call SuppressDefaultHostAuthentication and authenticate with the cookie, which in itself is weird because the app is setup to request these scopes. I would expect the cookie to contain that claim.

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
    Authority = identityServerUri,
    ClientId = ClientId,
    RedirectUri = redirectUri,
    Scope = "openid profile sampleApi",
    SignInAsAuthenticationType = "Cookies"
});

@B3nCr
Copy link
Author

B3nCr commented Oct 29, 2014

Okay, I think I've got it worked out. I've got two clients defined, one for the MVC server side application using a hybrid flow and another one for the javascript application using an implicit flow.

The token validation endpoint must have been expecting a token issued to my MVC application but it was returning a 401 when it received a token from my SPA. I thought that the token issued by identity server would work for any API which is configured to use my identity server as the authority so long as the token contained the appropriate scope.

I've switched to a hybrid flow and now all 3 scenarios work, MVC Views with OWIN redirecting, WebAPI access via token and also CORS request with the same token.

Thanks for all your help everyone.

@mattcowen
Copy link

Thanks for sharing.

I wondered if you could point me in the right direction but my scenario is
slighly different to yours. I know I will have an implicit client for my
app but I also want to have an app to app token for my website and user
store application to communicate securely (password resets and adding users
etc). What sample is best to look at? I don't want my web client users to
have direct access to the user store with their cookie but I do want the
website itself to have secure access. I'm not quite sure where to start but
I'm sure one of these samples has the answer.

On Wed, Oct 29, 2014 at 10:47 PM, B3nCr [email protected] wrote:

Okay, I think I've got it worked out. I've got two clients defined, one
for the MVC server side application using a hybrid flow and another one for
the javascript application using an implicit flow.

The token validation endpoint must have been expecting a token issued to
my MVC application but it was returning a 401 when it received a token from
my SPA. I thought that the token issued by identity server would work for
any API which is configured to use my identity server as the authority so
long as the token contained the appropriate scope.

I've switched to a hybrid flow and now all 3 scenarios work, MVC Views
with OWIN redirecting, WebAPI access via token and also CORS request with
the same token.

Thanks for all your help everyone.


Reply to this email directly or view it on GitHub
#487 (comment)
.

@B3nCr
Copy link
Author

B3nCr commented Oct 30, 2014

I don't think you want to use the ClientCredentials flow to talk app to app for admin functionality. It'll still be a user who is performing the admin actions.

You could map your admin API separately to your main API and require a different scope but I don't think that's how Identity Server works. I don't know yet if there's a way to deny certain users certain scopes.

You probably want to do claims transformation when your admin user successfully logs in/authenticates with your API. Map the identity server identity to a local user and store privilege information within your app.

Perhaps one of the contributors to the project knows which sample is best. I've mostly been following documentation and blogs rather than code samples.

@jake1164
Copy link

jake1164 commented Nov 3, 2014

@B3nCr I am having a very similar issue and wondered if you could just update your final changes to get this working?

For the two clients you just switched the Flow to Hybrid in both?
Did you end up using SuppressDefaultHostAuthentication() and Filters?

I think I am missing something small and just have not put my finger on what it is yet.

Thanks, J

@B3nCr
Copy link
Author

B3nCr commented Nov 3, 2014

@jake1164 Yes I did SuppressDefaultHostAuthentication(). If I didn't then the API would have allowed cookie authentication and we would have had to mitigate XSRF somehow and I'd rather just use a token.

I changed the two clients into one client. The AngularJS application and the MVC application were both running on the same domain so they're both a single app according to Identity Server. One other thing I had to change was I needed to set the response_type to code id_token token.

I created a distilled sample to show people at work a site which used the cookie middleware to authenticate an ASP.NET MVC view and a token for use in an Angular JS application which can be used to call a local API (in the same project as the view) and also make a CORS request to another API. I was planning on sharing it in a GitHub repo, I'll do it tonight.

@mattcowen
Copy link

That would be fab
On 3 Nov 2014 17:03, "B3nCr" [email protected] wrote:

@jake1164 https://github.com/jake1164 Yes I did
SuppressDefaultHostAuthentication(). If I didn't then the API would have
allowed cookie authentication and we would have had to mitigate XSRF
somehow and I'd rather just use a token.

I changed the two clients into one client. The AngularJS application and
the MVC application were both running on the same domain so they're both a
single app according to Identity Server. One other thing I had to change
was I needed to set the response_type to code id_token token.

I created a distilled sample to show people at work a site which used the
cookie middleware to authenticate an ASP.NET MVC view and a token for use
in an Angular JS application which can be used to call a local API (in the
same project as the view) and also make a CORS request to another API. I
was planning on sharing it in a GitHub repo, I'll do it tonight.


Reply to this email directly or view it on GitHub
#487 (comment)
.

@B3nCr
Copy link
Author

B3nCr commented Nov 3, 2014

I was bored on a train so I've done it already. https://github.com/B3nCr/IdentityServer-Sample

It could be a little cleaner but there's not a great deal of code so you should be able to see everything.

The authentication request in the JS app in the register controller and identity server redirects back to the login controller.

@B3nCr
Copy link
Author

B3nCr commented Nov 3, 2014

If you want to run it you'll have to generate your own certificates and setup IIS.

@richtea
Copy link

richtea commented Nov 4, 2014

@B3nCr Thanks very much for your sample. I had a bit of head-scratching to work out how to create and install the right certs and configure IIS, but got it running in the end.

For anyone else trying to run the sample, here's a brief run-down of the steps I needed:

  • Create certificates for b3ncr.auth, b3ncr.comm, and b3ncr.comm.api. I used the PluralSight certificate tool to create and install these certs directly in the My (aka Personal) store in LocalMachine.
  • Using the MMC Certificates snap-in, copy the certs from the My store and paste them into the Root store (also called Trusted Root Certification Authorities in the snap-in).
  • Modify the file permissions of the private key files in the My store to grant read access to NETWORK_SERVICE (or whatever user is running the app pool of the b3ncr.auth site).
  • Set up IIS sites and SSL bindings for each of the projects, using the certs I created earlier when configuring the SSL bindings. The projects and domain names map as follows:
    • b3ncr.auth -> B3nCr.Identity
    • b3ncr.comm -> B3nCr.Communication
    • b3ncr.comm.api -> B3nCr.Communication.Api

I also needed to change the SSL ports as they conflicted with some pre-existing Thinktecture samples.

@leastprivilege
Copy link
Member

Just in general - i would recommend separating web app and api - that way you don't run into the whole cookie vs token isolation issue and the related configuration complexity.

@B3nCr
Copy link
Author

B3nCr commented Nov 4, 2014

For a single page app does the CORS configuration complexity in the API not cancel out the SuppressDefaultHostAuthentication configuration complexity?

I guess with a separate API you have other pros/cons. You can scale the API separately from the UI (in a single page app) for example.

@leastprivilege
Copy link
Member

Well - I just think you should treat front end and back end as separate entities. Both approaches have their ups and downs of course.

@jlegan
Copy link

jlegan commented Jan 26, 2015

@B3nCr In your sample SuppressDefaultHostAuthentication is commented out. Is that intentional? The thread emphasizes its importance to avoid needed to mitigate XSRF yourself.

I am in a similar boat. I started this journey with the MVC sample which was great but I also need to expose several API end points for user registration for my identity provider. I have heavily customized the views (displaying custom user logins based on the originating source) and it is all working great except for the melding of the API using UseIdentityServerBearerTokenAuthentication and the MVC front end using UseOpenIdConnectAuthentication.

@B3nCr
Copy link
Author

B3nCr commented Jan 26, 2015

It's months since I've looked at this so I can't remember. My sample is
definitely out of date, there is a new version of identity server now.
There were a few workarounds in there which were to mitigate bugs with the
scope validation but I don't think that's what you're referring to.
On 26 Jan 2015 14:45, "James Legan" [email protected] wrote:

@B3nCr https://github.com/B3nCr In your sample
SuppressDefaultHostAuthentication is commented out. Is that intentional?
The thread emphasizes its importance to avoid needed to mitigate XSRF
yourself.

I am in a similar boat. I started this journey with the MVC sample which
was great but I also need to expose several API end points for user
registration for my identity provider. I have heavily customized the views
(displaying custom user logins based on the originating source) and it is
all working great except for the melding of the API using
UseIdentityServerBearerTokenAuthentication and the MVC front end using
UseOpenIdConnectAuthentication.


Reply to this email directly or view it on GitHub
#487 (comment)
.

@bychkov
Copy link

bychkov commented Apr 20, 2015

Can anybody point me in the direction of the working sample with separate configurations for MVC and WebAPI using IdS?

@brockallen
Copy link
Member

@bychkov have you looked at the clients solution (from the samples repo)? this shows an API project and many many client applications. They're all designed to work with the sample host for idsvr from the idsvr repo.

@gilm0079
Copy link

I've been struggling with getting my api to read the bearer tokens coming in my from angular app. I've followed the suggestions here, but when the api call is made via angular to the api it only lets me reach the api if I use the AllowAnonymous attribute. Once inside the Principal Identity looks to have 1 item, but it is missing all related claims and authenticated is false. If I try to use the Authorize attribute or ResourceAuthorization attribute both do not work and result in a 401 returned to the app.

The API is separated from the hosting MVC app and mapped in via the OWIN AppBuilder Map. I followed the above suggestions, but I'm not getting the same results. Using fiddler I can see the bearer token in the Http request so I know that it is reaching the server. Any ideas on how to further troubleshoot this? I'm at a loss.

I had this issue open as well. It is for the same issue, but I originally though there was a problem with the ResourceAuthorization attribute. After pulling down the source and stepping in to the CheckAccess I found the Principal was not being set correctly which was causing the failure.

https://github.com/IdentityModel/Thinktecture.IdentityModel/issues/122

Ideas anyone? Thanks in advance.

@gilm0079
Copy link

One more note. I've scrubbed through all the sample projects trying to find one with a similar structure. Is it possible this issue stems from my API project being a separate class library vs. a hostable API project? I originally saw this structure used in the Identity Manager and liked how it allowed for project separation, but still ran as part of the hosting project. It would be similar to how the identity server is a Mapped into a web project.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

9 participants