For user authentication with external providers such as Google, it is using specific Owin middlewares. As for example Microsoft.Owin.Security.Google. WebAPI2 template uses this to support implicit flow authentication (response_type=token). But what about Code flow?
Is it possible to implement Code flow (response_type=code)?
After debugging those OAuth providers I noticed that passing return_type=code to Google, it successfully authenticates and returns json with access and refresh tokens, then user gets signed in by api/Account/ExternalLogin endpoint but at the end of the flow I get redirected to
http://localhost:50321/?error=unsupported_response_type#.
I could not really find the flow where and why it is setting this specific error in the assembly.
Startup.Auth.cs looks like this:
public void ConfigureAuth(IAppBuilder app)
{
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
PublicClientId = "self";
var tokenTimeSpanInHours = ConfigurationManager.AppSettings["AccessTokenLifeTimeInHours"];
OAuthServerOptions = new OAuthAuthorizationServerOptions
{
Provider = new ApplicationOAuthProvider(PublicClientId),
TokenEndpointPath = new PathString("/api/token"),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(Convert.ToInt16(tokenTimeSpanInHours)),
AllowInsecureHttp = true
};
app.UseOAuthBearerTokens(OAuthServerOptions);
var googleOAuthOptions = new GoogleOAuth2AuthenticationOptions
{
AccessType = "offline",
Provider = new CustomGoogleAuthProvider(),
ClientId = ConfigurationManager.AppSettings["GoogleAccountClientId"].ToString(),
ClientSecret = ConfigurationManager.AppSettings["GoogleAccountClientSecret"].ToString()
};
googleOAuthOptions.Scope.Add("profile");
googleOAuthOptions.Scope.Add("email");
googleOAuthOptions.Scope.Add("https://www.googleapis.com/auth/gmail.send");
app.UseGoogleAuthentication(googleOAuthOptions);
}
Where is the problem then? Do I need some explicit configuration to tell that I want code flow? Is it supported?
Related
I'm trying to implement the sample client server in my current MVC apps to use openiddict.
I have a client App, that I configured to the best of my knowledge the same way as the MvcClient app in the samples project (mortis.client).
This seems to work.
I have an authentication app, which is used to authenticate our users across multiple applications, I integrated the mortis.server example in it. But I'm pretty sure I missed some stuff, here is what happens:
The client app requests access to the server app, it works, the connect/authorize method is called in the AuthorizeController of the server app, then, it requests the login of the user first (if not loggued in). After that, connect/authorize is called again, logs signs in the user and builds the identity and it passes in the implicit consent type switch/case.
After that, there is a redirect to the connect/token method but my McvServer apps redirects to login (seems the user is not really logued in?)
What am I doing wrong?
This is my Client App setup
public void ConfigureAuth(IAppBuilder app)
{
IdentityModelEventSource.ShowPII = true;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = "https://url/serverapp/"
ClientId = "MyClient.MVC",
ClientSecret = "myclientsecret",
RedirectUri = $"https://url/clientapp/signin-oidc",
RedeemCode = true,
ResponseMode = OpenIdConnectResponseMode.Query,
ResponseType = OpenIdConnectResponseType.Code,
Scope = "openid profile email roles",
SecurityTokenValidator =
new JwtSecurityTokenHandler
{
// Disable the built-in JWT claims mapping feature.
InboundClaimTypeMap = new Dictionary<string, string>()
},
TokenValidationParameters =
new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
Notifications =
new OpenIdConnectAuthenticationNotifications
{
// Note: by default, the OIDC client throws an OpenIdConnectProtocolException
// when an error occurred during the authentication/authorization process.
// To prevent a YSOD from being displayed, the response is declared as handled.
AuthenticationFailed =
notification =>
{
if (string.Equals(notification.ProtocolMessage.Error, "access_denied", StringComparison.Ordinal))
{
notification.HandleResponse();
notification.Response.Redirect("/");
}
return Task.CompletedTask;
},
AuthorizationCodeReceived = AuthorizationCodeReceived,
MessageReceived = OnMessageReceived,
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
SecurityTokenReceived = OnSecurityTokenReceived,
SecurityTokenValidated = OnSecurityTokenValidated,
TokenResponseReceived = OnTokenResponseReceived
}
});
}
The signin link is the same as in the sample app (a sign in view that challenges owin access)
Here is the server configuration:
public void Configuration(IAppBuilder app)
{
// SignalR
app.MapSignalR();
ConfigureAuth(app);
ConfigureOpenIddict(app);
}
private void ConfigureOpenIddict(IAppBuilder app)
{
var container = CreateContainer();
// Register the Autofac scope injector middleware.
app.UseAutofacLifetimeScopeInjector(container);
// Register the two OpenIddict server/validation middleware.
app.UseMiddlewareFromContainer<OpenIddictServerOwinMiddleware>();
app.UseMiddlewareFromContainer<OpenIddictValidationOwinMiddleware>();
// Configure ASP.NET MVC 5.2 to use Autofac when activating controller instances.
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// Seed the database with the sample client using the OpenIddict application manager.
// Note: in a real world application, this step should be part of a setup script.
Task.Run(() => this.FillClients(container));
}
private static IContainer CreateContainer()
{
var services = new ServiceCollection();
services.AddOpenIddict()
// Register the OpenIddict core components.
.AddCore(
options =>
{
// Configure OpenIddict to use the Entity Framework 6.x stores and models.
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
options
.UseEntityFramework()
.UseDbContext<AppDbContext>();
})
// Register the OpenIddict server components.
.AddServer(
options =>
{
// Enable the authorization, logout and token endpoints.
options
.SetAuthorizationEndpointUris("/connect/authorize")
.SetLogoutEndpointUris("/connect/logout")
.SetTokenEndpointUris("/connect/token");
// Mark the "email", "profile" and "roles" scopes as supported scopes.
options.RegisterScopes(Scopes.Email, Scopes.Profile, Scopes.Roles);
// Note: this sample only uses the authorization code flow but you can enable
// the other flows if you need to support implicit, password or client credentials.
options.AllowAuthorizationCodeFlow();
// Register the signing and encryption credentials.
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
// Register the OWIN host and configure the OWIN-specific options.
options.UseOwin()
.EnableAuthorizationEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableTokenEndpointPassthrough();
})
// Register the OpenIddict validation components.
.AddValidation(
options =>
{
// Import the configuration from the local OpenIddict server instance.
options.UseLocalServer();
// Register the OWIN host.
options.UseOwin();
});
// Create a new Autofac container and import the OpenIddict services.
var builder = new ContainerBuilder();
builder.Populate(services);
// Register the MVC controllers.
builder.RegisterControllers(typeof(Startup).Assembly);
return builder.Build();
}
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext<AppDbContext>((options, context) => AppDbContext.Create());
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(
new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
CookieName = "CookieNameBla_SessionId",
SlidingExpiration = true,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
CookieHttpOnly = true,
CookiePath = "/",
CookieSecure = this.ServiceEnvironment == ServiceEnvironment.Development ? CookieSecureOption.SameAsRequest : CookieSecureOption.Always,
ExpireTimeSpan = TimeSpan.FromDays(14),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
}
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process.
app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
// Enables the application to remember the second login verification factor such as phone or email.
// Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from.
// This is similar to the RememberMe option when you log in.
app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
In Web Apis, I always use given below URL to get access token.
http://localhost:port/token
But I cannot find token in MVC project. I try to find it over internet but unfortunately, does not find any solution.
The url/token is not reserved value, it's configurable in code in Startup.Auth.cs file in the OAuthAuthorizationServerOptions for example :
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(20),
RefreshTokenProvider = new OAuthCustomRefreshTokenProvider(),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true,
AccessTokenFormat = new TokenManager(),
};
whatever value you specify in the TokenEndpointPath, it's used in the URL to retrieve the access/refresh tokens
I am trying to do OAuth with Google on an MVC Web API 2 project. Whenever I call the GetExternalLogin API Method I keep getting this: error: invalid_request.
I have everything setup on the google site correctly as I tested the same configuration with another MVC application and it worked perfectly.
I have made no changes to the code generated other than putting in the app.UseGoogleAuthentication with the ClientId and ClientSecret.
Regardless here is my ConfigureAuth:
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
OAuthBearerOptions.AccessTokenFormat = OAuthOptions.AccessTokenFormat;
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()
{
ClientId = "My Key",
ClientSecret = "Secret"
});
When I go through and debug I get to the ApplicationOAuthProvider.cs class and the ValidateClientRedirectUri method which is turning out to be the problem.
The method looks like so:
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
context.Validated();
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
When I put a break point entering the method I notice that everything in the context is null. So every time the context.ClientId will also be null and then I get the error: invalid_request message.
I read that it was because context.IsValid is set to false so I tried to call context.Validated(); as the first thing in the method and then just returning return Task.FromResult<object>(null);. However, that did not work as even when I call context.Validated() the context does not change IsValid to true is stays false.
What am I doing wrong here?
Hi i am using kentor auth services(The Kentor Authentication services is a library that adds SAML2P support to ASP.NET and IIS web sites, allowing the web site to act as a SAML2 Service Provider (SP) ).Right now i am using Google as a Identity Privider for testing my application (Authentication using owin midddleware).I have set Up Google Identity provider also.But When i run the application it gives me an error
"400. That’s an error.
Invalid Request, invalid idpId in request URL, check if SSO URL is configured properly on SP side. That’s all we know."
i have used SingleSignOnServiceUrl=https://accounts.google.com/o/saml2/idp?idpid=xxxxxxxxx
DiscoveryServiceUrl=https://accounts.google.com/o/saml2/idp?idpid=xxxxxxxxx
Is that above configuration is correct?
I have attached App_start configuration below.This from Kentor auth services library.
public partial class Startup
{
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseKentorAuthServicesAuthentication(CreateAuthServicesOptions());
}
private static KentorAuthServicesAuthenticationOptions CreateAuthServicesOptions()
{
var spOptions = CreateSPOptions();
var authServicesOptions = new KentorAuthServicesAuthenticationOptions(false)
{
SPOptions = spOptions
};
var idp = new IdentityProvider(new EntityId("~/App_Data/GoogleIDPMetadata.xml"), spOptions)
{
AllowUnsolicitedAuthnResponse = true,
Binding = Saml2BindingType.HttpRedirect,
SingleSignOnServiceUrl = new Uri("https://accounts.google.com/o/saml2/idp?idpid=xxxxxxxxx")
};
idp.SigningKeys.AddConfiguredKey(
new X509Certificate2(
HostingEnvironment.MapPath(
"~/App_Data/Kentor.AuthServices.StubIdp.cer")));
authServicesOptions.IdentityProviders.Add(idp);
// It's enough to just create the federation and associate it
// with the options. The federation will load the metadata and
// update the options with any identity providers found.
new Federation("http://example.com/Federation", true, authServicesOptions);
return authServicesOptions;
}
private static SPOptions CreateSPOptions()
{
var swedish = CultureInfo.GetCultureInfo("sv-se");
var organization = new Organization();
organization.Names.Add(new LocalizedName("Kentor", swedish));
organization.DisplayNames.Add(new LocalizedName("Kentor IT AB", swedish));
organization.Urls.Add(new LocalizedUri(new Uri("http://www.kentor.se"), swedish));
var spOptions = new SPOptions
{
EntityId = new EntityId("https://example.com/AuthServices"),
ReturnUrl = new Uri("https://example.com/Account/ExternalLoginCallback"),
DiscoveryServiceUrl = new Uri(https://accounts.google.com/o/saml2/idp?idpid=xxxxxxxxx"),
Organization = organization
};
var techContact = new ContactPerson
{
Type = ContactType.Technical
};
techContact.EmailAddresses.Add("authservices#example.com");
spOptions.Contacts.Add(techContact);
var supportContact = new ContactPerson
{
Type = ContactType.Support
};
supportContact.EmailAddresses.Add("support#example.com");
spOptions.Contacts.Add(supportContact);
var attributeConsumingService = new AttributeConsumingService("AuthServices")
{
IsDefault = true,
};
attributeConsumingService.RequestedAttributes.Add(
new RequestedAttribute("urn:someName")
{
FriendlyName = "Some Name",
IsRequired = true,
NameFormat = RequestedAttribute.AttributeNameFormatUri
});
attributeConsumingService.RequestedAttributes.Add(
new RequestedAttribute("Minimal"));
spOptions.AttributeConsumingServices.Add(attributeConsumingService);
spOptions.ServiceCertificates.Add(new X509Certificate2(
AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "/App_Data/Kentor.AuthServices.Tests.pfx"));
return spOptions;
}
Why i am getting 400 error when i redirect to google saml page? Thanks in advance
AFAIK Google offers no discovery service. Remove the DiscoveryServiceUrl from the configuration.
Also you should really clean up the configuration and not use the sample application's config.
For testing you can also use the Stub idp that is included in the project at which is available at http://stubidp.kentor.se
Based on this tutorial http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server, I have created an Authorization Server, a Resource Server and a MVC Client.
The MVC Client has a Controller which gets some data from the Resource Server. The Resource Server requires authentication. The MVC Clients gets an authorization code from the Authorization Server and Redirects the user to the Authorization Server for authentication. Finally the MVC Clients exchanges the authorization code for a Access token to Access the Resource Server. This is the Authorization code flow as described by the OAuth 2 protocol. This works fine.
Now, I have the requirement to make a Controller of the MVC Client itself require Authentication. I can not find a tutorial for this.
I added
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
to my Startup.Auth.cs.
I assume, I need to setup the Options to Redirect to the Authorization Server. I can also set the Provider on the Options:
app.UseOAuthBearerAuthentication(new Microsoft.Owin.Security.OAuth.OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
});
But I am also stuck on implementing the events of the Provider.
Can anybody guide me in the right direction? Or are there any tutorials which might help me?
I ended up with a solution based on these two articles from Brock Allen:
http://brockallen.com/2013/10/24/a-primer-on-owin-cookie-authentication-middleware-for-the-asp-net-developer/
http://brockallen.com/2014/01/09/a-primer-on-external-login-providers-social-logins-with-owinkatana-authentication-middleware/
The fundemental idea is to register two authentication Middlewares. An active Cookie-Authentication and a passive OAuthBearer-Authentication. In Startup.Auth.cs they are added like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/ExternalLogin/Login"),
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.ExternalBearer,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
You also add an ExternalLogin-Controller. Its Login-method has to redirect the user to the Login-page of your Authorization Server to get the authorization code. You have to supply a callback function where you will process the authorization code.
public async Task<ActionResult> Login(string returnUrl)
{
if (string.IsNullOrEmpty(returnUrl) && Request.UrlReferrer != null)
returnUrl = Server.UrlEncode(Request.UrlReferrer.PathAndQuery);
if (Url.IsLocalUrl(returnUrl) && !string.IsNullOrEmpty(returnUrl))
_returnUrl = returnUrl;
//callback function
_redirectUrl = Url.Action("AuthorizationCodeCallback", "ExternalLogin", null, Request.Url.Scheme);
Dictionary<string, string> authorizeArgs = null;
authorizeArgs = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"response_type", "code"}
,{"scope", "read"}
,{"redirect_uri", _redirectUrl}
// optional: state
};
var content = new FormUrlEncodedContent(authorizeArgs);
var contentAsString = await content.ReadAsStringAsync();
return Redirect("http://localhost:64426/oauth/authorize?" + contentAsString);
}
In your callback-function you exchange the authorization code for an access token (plus refresh token) challenge your passive OAuthBearer-authentication Middleware and signin with the Access token as your Cookie.
public async Task<ActionResult> AuthorizationCodeCallback()
{
// received authorization code from authorization server
string[] codes = Request.Params.GetValues("code");
var authorizationCode = "";
if (codes.Length > 0)
authorizationCode = codes[0];
// exchange authorization code at authorization server for an access and refresh token
Dictionary<string, string> post = null;
post = new Dictionary<string, string>
{
{"client_id", "0123456789"}
,{"client_secret", "ClientSecret"}
,{"grant_type", "authorization_code"}
,{"code", authorizationCode}
,{"redirect_uri", _redirectUrl}
};
var client = new HttpClient();
var postContent = new FormUrlEncodedContent(post);
var response = await client.PostAsync("http://localhost:64426/token", postContent);
var content = await response.Content.ReadAsStringAsync();
// received tokens from authorization server
var json = JObject.Parse(content);
_accessToken = json["access_token"].ToString();
_authorizationScheme = json["token_type"].ToString();
_expiresIn = json["expires_in"].ToString();
if (json["refresh_token"] != null)
_refreshToken = json["refresh_token"].ToString();
//SignIn with Token, SignOut and create new identity for SignIn
Request.Headers.Add("Authorization", _authorizationScheme + " " + _accessToken);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync(DefaultAuthenticationTypes.ExternalBearer);
ctx.Authentication.SignOut(DefaultAuthenticationTypes.ExternalBearer);
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
var ctxUser = ctx.Authentication.User;
var user = Request.RequestContext.HttpContext.User;
//redirect back to the view which required authentication
string decodedUrl = "";
if (!string.IsNullOrEmpty(_returnUrl))
decodedUrl = Server.UrlDecode(_returnUrl);
if (Url.IsLocalUrl(decodedUrl))
return Redirect(decodedUrl);
else
return RedirectToAction("Index", "Home");
}
I hope this is useful for someone who is implementing the OAuth authorization code flow in his MVC 5 application.
I used official sample MVC Implicit Client which I believe is the correct authentication flow for MVC application.
For authorization I used this getting started, especially the part about infinite loop when roles are specified [Authorize(Roles = "Foo,Bar")] and user is authenticated but doesn't own any of these.