Aspnet Core 3.1 MVC Razor pages gets 302 after authenticating with Identity Server 4 OpenIdConnect - .net-core

I have an MVC Razor pages app which I want to hook into our Identity Server Implementation. We have followed the tutorial in their quickstart https://identityserver4.readthedocs.io/en/latest/quickstarts/2_interactive_aspnetcore.html and got it working in a brand new project, so we assume the client and IDS configuration is OK.
However, when we port it into our RazorPages application we get into a loop. We are sent off to the IDS, we log in and we're sent back to the signin-oidc page. This page seems to generate a 302.
Please see this network trace. Each time the request is made a new "code_challenge" parameter is requested
My startup is (sorry long and) here:
public void ConfigureServices( IServiceCollection services )
{
services.AddIdentity<ApplicationUser, IdentityRole>( options =>
{
options.SignIn.RequireConfirmedEmail = true;
} )
.AddEntityFrameworkStores<CourseRegContext>()
.AddDefaultTokenProviders();
var cs = Configuration.GetConnectionString( "DefaultConnection" );
var skipHTTPS = Configuration.GetValue<bool>( "LocalTest:skipHTTPS" );
services.Configure<MvcOptions>( options =>
{
if ( /*Environment.IsDevelopment() && */!skipHTTPS )
{
options.Filters.Add( new RequireHttpsAttribute() );
options.EnableEndpointRouting = false;
}
} );
services.AddMvc()
.SetCompatibilityVersion( CompatibilityVersion.Version_2_1 )
.AddNewtonsoftJson(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
services.AddMvc( config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add( new AuthorizeFilter( policy ) );
config.EnableEndpointRouting = false;
} ).AddRazorPagesOptions( options =>
{
options.Conventions.AllowAnonymousToFolder( "/Oops" );
options.Conventions.AuthorizeFolder( "/Test" );
} );
services.AddMemoryCache();
services.AddHttpsRedirection( options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
if ( Environment.IsDevelopment() )
{
options.HttpsPort = 44311;
}
else
{
options.HttpsPort = 443;
}
} );
services.AddHsts( options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromHours( 1 );
} );
AddAuthorisation( services );
}
private void AddAuthorisation( IServiceCollection services )
{
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication( options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
} )
.AddCookie( "Cookies" )
.AddOpenIdConnect( "oidc", options =>
{
options.Authority = "https://localhost:41012";
options.ClientId = "validId";
options.ClientSecret = "somesecret";
options.ResponseType = "code";
options.Scope.Add( "roles" );
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
//options.UsePkce = true;
} );
//services.AddAuthorization();
services.AddAuthorization( options =>
{
options.AddPolicy( AuthPolicies.GlobalAdmin, policy =>
policy.RequireRole( ApplicationRoles.Administrator ) );
options.AddPolicy( AuthPolicies.CourseAdmin, policy =>
policy.RequireRole(
ApplicationRoles.Administrator,
ApplicationRoles.CourseAdmin ) );
options.AddPolicy( AuthPolicies.Presenter, policy =>
policy.RequireRole( ApplicationRoles.CourseViewer ) );
options.AddPolicy( AuthPolicies.UserAdmin, policy =>
policy.RequireRole( ApplicationRoles.UserAdmin, ApplicationRoles.Administrator ) );
} );
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure( IApplicationBuilder app, IHostingEnvironment env )
{
if ( env.IsDevelopment() )
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler( "/Oops/C500" );
app.UseHsts( options => options.MaxAge( hours: 1 ).IncludeSubdomains() ); //todo when confident it is working, use 180 days
}
app.UseStatusCodePagesWithRedirects( "/Oops/C{0}" );
app.UseXXssProtection( options => options.EnabledWithBlockMode() );
app.UseXContentTypeOptions();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints( endpoints =>
{
//endpoints.MapDefaultControllerRoute()
//.RequireAuthorization();
} );
app.UseMvc();
}
I wonder if it is something do with MVC and EndPoints as part of the upgrade to .NET Core 3.1, but I am unsure how to keep the rest of the project working and yet still get the IDS integration.

I started a blank project and got it authenticating with no other code attached and it worked. So I then put each line back fixing each DI error until I saw the error again. It was to do with services.AddIdentity<ApplicationUser, IdentityRole> which I guess makes sense as it adds login authentication stuff as well as the managers. So, this line needs to be removed, but also need to remove all references to the usermanager etc too.
One mass refactor later and have now broken the redirect to IDS, but hopefully will get it redirecting. (it's now just acting like there is no authentication at all)

Related

.Net Core with WS-Federation generates large wctx

I am attempting to learn how to use WS-Federation in a .NET Core application. I am attempting a basic step of having my client application redirect to an authentication service (defined in my FederationMetadata.xml file), but when I attempt to access a controller requiring authentication [url in this case is http://localhost/STSAwareApp/Test], my redirect url becomes too large to be usable (I get a 404.15, query string is too large).
Since I'm new to WS-Federation, I'm assuming that I have a configuration issue in my startup:
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services.AddControllersWithViews();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddWsFederation(authenticationScheme: "WsFederation", displayName: "Test WS-Fed", options =>
{
options.Wtrealm = "http://localhost/STSAwareApp/Test";
options.MetadataAddress = "http://localhost/STSAwareApp/files/FederationMetadata.xml";
options.RequireHttpsMetadata = false;
}).AddCookie(options =>
{
options.Cookie.Name = "TestStsAuth";
options.Cookie.HttpOnly = true;
});
services.AddLogging(
builder =>
{
builder.AddFilter("Microsoft", LogLevel.Trace)
.AddFilter("System", LogLevel.Trace)
.AddConsole();
});
}
}
Here is an example of the redirect URL that is getting generated (just for completeness sake):
http://localhost:80/STSAwareApp/Test?wtrealm=http%3A%2F%2Flocalhost%2FSTSAwareApp%2FTest&wa=wsignin1.0&wreply=http%3A%2F%2Flocalhost%2FSTSAwareApp%2Fsignin-wsfed&wctx=CfDJ8O7dpxEY6MBCgxct4kkpp1gFIwYvsJN7p6zOuAiyltKmCqff605h1uCh7ZBNM6WneU_7XlxHKAt7CYmBdXG_e19L8z-p64d21gJjDJCdjOkfNieQWNRSPQPGZDUL8eBEVqs4vWaKN-sof8lnblDbySiP8NJPR945c8IYqRwaf7ZBZ-_IxoWZLN_OgMOgFnU5XjtDeUfFCcHh0dtGwSc4PVDPxhKIpxb3JyIEMBRA19qZpudqQEylX6WHek5LkNK1IDbWDv2ll9F5HCJSQxvpVDrLw62dBfF6IDNg3Ar8q2Yr_bpV1gA1RR7kHp3Gs4soxfZENfvi96qkPJs4ZOqvUYjRQjho34Lkc9VH5q2w7n4Oty6abFXs_jeDQQN7ZyFBGQrb-wxBZBEuvNJAFp-ckhGVCeKrtdmXS4bVAvbEtPAEtLHXJpv82Y843_UVCeAQycMjmz2stIovI-HiKAWwCkoc03J7gOlTEwyrn1cR-Ia3QWN4mPN2ncqxW5e80kamNDIDmRxiWoox1Z6x5SATSIO3KergXc7VE1G8-2gLicc8_flyLR6NXUAdDRZTnxGzChHzf2L1eqjm0K_PvioAdqJNuFDlFMeGyfarEbXahAqpchuDvSgolSEKgGO-uLw5GEdCS-5cX_Ztt3bAjbXzkPMdhzYbXFWTDdYTMMMta18nhzgAk5CIzDvo1BmniWGdwUy-lAWm9BoNd4TsroQa-F8NJ86K4sixQIqRqQ-D-Bf_672hHbIkY1QEEe8tqTH-1Qwn9K5RY5sVFQLu0Ec4bp0Zj2EDis-GAtMxhp6761MciYjjhqgORhe3gsLeej5GEY0AErXUOCxdghQKs-waLQtNQ2F4Xn226DYp6NVn8bLs5pu4mFblaWRn9cVzKPHUosRT9BjKqbnLpCCC0A4cOXec-G5znWLOXa6G4qsZjFl5h79MwStDnzP6GU2Wg6TaLG83783f6bRsJwX8blc1CMEjByphkpZp-VdR6FytLXvu4bh8gQQo2K3ad76pNlF8HnA4y1f0p86A82i2IPPDrOeW6YFupzZRITSFz-JvhjAZbkSzu26bgqgHNVTIz1ebu9mHIMQzGzpAu0rFIl16HszR7Omxn8TljADTCCLasQyLNRUIXSA5teeowULetXEv_rmOr6ANkk0kQ-q3pPuiOzkA0aFV6g1jYQ-JvS9K817IafEes7akoDrPbeHEmvD5sWzxERlMtnEQtYwcrPiOroWXIh1QgLjqUgTxtagWmkzoBWVM5PnNmMVkk0alyTgZKOomTcZN8ePkLRp4sY0d0D_uqb0Rn_s757Nb-oDztAz6SLOkCzWnPDif3eIAFTZy24v_oYr3SOFfvM2J-_t0kg3zlRovg25_bPPSs-qyfrMMBSbMammB5e7SKbIna4dPhMdv93Vm6I2GwJ8-VY-pAuBT4MQXPLD1VwdiBT3hWsZOoeMUl1JuL7B9pJDAMBNO2OUTaRb7dajP3VsA09XSgVrBeZ1Hvk733TrzFVoR5KQgHS4qw9cxquRmqP2XfEYTQocB-mUL4b-n0h3RN2qzaHn_VH2pZDV842YcanF4SZ8dDPB4EnLCWU7pf67IwvruInvu8MXg01xNoURh6rKLmSwikbgsEM7Es87RMQSEvar1QixBId9XMO1YiHVvGAdJoivUveJSO1T8Aj4A2xFllBjtD4SfnJc5UDTQ7UxGnVmIVw6pwS9N26U_u09n-T4j5R-ZVQyCNgSjoNRg-3jmMatXcAhT4vJgO-kRuzMiBKnavJ7EPyS8Th8KUK0ws1tQYQKmQQGvd7DT_GRC0wXT8HrTZ1uxTmxxDibzyCLxJZmulLHPcYaXwpWw6j56vOxgCrGy-3L5GtfnXNN1UdE3QzbE6_XL3xF8B3uD6Z5g5ZB_ZR4Q0QS0K9Kb6guaAtxEJYKc2eE2DZ2OpMNtyw5imNYt9crd5J4mB05GR7c0Nur2vqzk1mGM56_0IQD9L4HV4fXNmQuprEpwNZ41NyW-bhcVS30rZn73WLc-XBlNWhCrE_HiTWzCDOn9juofX7_C2AcQypJt-aweXEN5uxRWPp_W9qFJNblrkjzAEr3o7_dylYLYTstOvW4dYuIE4WlTUiJdJF3Iy02whGQUpclOINsxZ3wotkKY2JsnUzsolSeIfWe-es8soGOkPnDSthgjRbpTxltmVz10L0kAo4zckz4HvhEmziWPsGWZH1UVtRKLniT60qq9PPxeuu_dsodov-ByanyRwMHlkzCJhmSBDE0
I'm under the assumption that the url is not only incorrect because the wctx is too long, but it should be attempting to redirect to a different service [http://localhost/STS/V1], it is attempting to the original url with the federation parameters in the query string. I think that the wctx should be smaller, because even if I increase the size of my URL limits, it just continues to grow.
The issue with this was that the FederationMetadata.xml document was not configured correctly. Not exactly sure what was wrong, but instead of attempting to have a valid xml document, it was simpler to update the Configuration property of the WsFederationOptions. Attached is the updated Startup call:
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services.AddControllersWithViews();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddWsFederation(authenticationScheme: "WsFederation", displayName: "Test WS-Fed", options =>
{
WsFederationConfiguration configuration = new WsFederationConfiguration();
configuration.TokenEndpoint = "http://localhost/STSSpike/V1";
options.Configuration = configuration;
options.Wtrealm = "http://localhost/STSAwareApp/Test";
}).AddCookie(options =>
{
options.Cookie.Name = "TestStsAuth";
options.Cookie.HttpOnly = true;
});
services.AddLogging(
builder =>
{
builder.AddFilter("Microsoft", LogLevel.Trace)
.AddFilter("System", LogLevel.Trace)
.AddConsole();
});
}
Guessing that this is a pretty localized issue, so might want to close.

How to make ASP.NET/React app serve SPA from subpath?

I have a stock aspnetcore and reactjs app, generated from the starter template (dotnet new react). I would like the SPA app to be served from a subpath off the root url; e.g. instead of the sample app being https://localhost:5001/counter I'm looking for it to instead be served from https://localhost:5001/myapp/counter.
I changed the Startup.cs from:
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
to this:
app.Map(new Microsoft.AspNetCore.Http.PathString("/myapp"), appMember =>
{
appMember.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
});
This sort of works. If I browse to https://localhost:5001/myapp/ it appears to load the index.html, but the static files are attempting to load from the root path and not the subpath.
What needs to be changed so that the react app uses the subpath as the root? I'd like this to work both in the interactive VS dev environment and when deployed, likely on IIS. It seems like it's close but I'm missing something.
Sample demo of the solution is available here: https://github.com/petertirrell/mvc-spa-demo/tree/master/mvc-spa-demo
Thanks!
Start with moving app to sub-path by adding this to top of package.json:
"homepage": "/myapp/",
When running npm start inside ClientApp folder, app is now serving http://localhost:3000/myapp
Then change Startup.cs like this:
First remove
app.UseSpaStaticFiles()
then add
const string spaPath = "/myapp";
if (env.IsDevelopment())
{
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(spaPath)
|| ctx.Request.Path.StartsWithSegments("/sockjs-node"),
client =>
{
client.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.UseReactDevelopmentServer(npmScript: "start");
});
});
}
else
{
app.Map(new PathString(spaPath), client =>
{
// `https://github.com/dotnet/aspnetcore/issues/3147`
client.UseSpaStaticFiles(new StaticFileOptions()
{
OnPrepareResponse = ctx =>
{
if (ctx.Context.Request.Path.StartsWithSegments($"{spaPath}/static"))
{
// Cache all static resources for 1 year (versioned file names)
var headers = ctx.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromDays(365)
};
}
else
{
// Do not cache explicit `/index.html` or any other files. See also: `DefaultPageStaticFileOptions` below for implicit "/index.html"
var headers = ctx.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromDays(0)
};
}
}
});
client.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
spa.Options.DefaultPageStaticFileOptions = new StaticFileOptions()
{
OnPrepareResponse = ctx => {
// Do not cache implicit `/index.html`. See also: `UseSpaStaticFiles` above
var headers = ctx.Context.Response.GetTypedHeaders();
headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = TimeSpan.FromDays(0)
};
}
};
});
});
}
Don't forget to clear browser history before testing changes for the first time on e.g. Azure.
You can do so by having:
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
in your ConfigureServices and:
string spaPath = "/myapp";
if (env.IsDevelopment())
{
app.MapWhen(y => y.Request.Path.StartsWithSegments(spaPath), client =>
{
client.UseSpa(spa =>
{
spa.UseReactDevelopmentServer(npmScript: "start");
});
});
}
else
{
app.Map(new PathString(spaPath), client =>
{
client.UseSpaStaticFiles();
client.UseSpa(spa => {});
});
}
It should be noted that in development we use .MapWhen because .Map would cause your static files to be available at /myapp/myapp/[file] as opposed to /myapp/[file].
I combined some of each answer to get it working. You need to add the "homepage": "/myapp" to the package.json as well as the config changes to Startup.cs. I used the simpler config provided in Shoe's answer without all the extra caching and sockets directives, as I don't need those.
Because my application also used React Router for SPA routing under /myapp I also needed to add basename to the root BrowserRouter:
<BrowserRouter basename="/myapp" >...</BrowserRouter>

Why 404.html causes uncaught error during service worker caching?

I have implemented service worker in my web app and attempted to cache all html, css, js, and images file. My hosting service is firebase, after a successful deployment I tested if the service will work and the files will be cached, unfortunately the following error will do occur.
service-worker.js
let cacheName = 'my-tools-v1';
let filesToCache = [
'/',
'/css/app.css',
'/css/diffview.css',
'/css/json-lint.css',
'/css/materialize.css',
'/images/favicon.png',
'/images/icons/apple-touch-icon.png',
'/images/icons/apple-touch-icon-57x57.png',
'/images/icons/apple-touch-icon-72x72.png',
'/images/icons/apple-touch-icon-76x76.png',
'/images/icons/apple-touch-icon-114x114.png',
'/images/icons/apple-touch-icon-120x120.png',
'/images/icons/apple-touch-icon-144x144.png',
'/images/icons/apple-touch-icon-152x152.png',
'/images/icons/apple-touch-icon-180x180.png',
'/images/icons/icon-72x72.png',
'/images/icons/icon-96x96.png',
'/images/icons/icon-128x128.png',
'/images/icons/icon-144x144.png',
'/images/icons/icon-152x152.png',
'/images/icons/icon-192x192.png',
'/images/icons/icon-384x384.png',
'/images/icons/icon-512x512.png',
'/js/index.js',
'/js/css-lint.js',
'/js/difflib.js',
'/js/diffview.js',
'/js/ipsum-generator.js',
'/js/json2.js',
'/js/json-lint.js',
'/js/jsonlint.js',
'/js/lorem-ipsum.js',
'/js/materialize.js',
'/js/visual-difference.js',
'/bower_components/codemirror/lib/codemirror.js',
'/bower_components/codemirror/mode/javascript/javascript.js',
'/bower_components/codemirror/mode/css/css.js',
'/bower_components/codemirror/addon/edit/matchbrackets.js',
'/bower_components/codemirror/addon/comment/continuecomment.js',
'/bower_components/codemirror/addon/comment/comment.js',
'/bower_components/ft-csslint/dist/csslint.js',
'/bower_components/jquery/dist/jquery.slim.min.js',
'/bower_components/kanye-ipsum/dist/jquery.kanye-ipsum.min.js',
'/bower_components/codemirror/lib/codemirror.css',
'/index.html',
'/css-lint.html',
'/ipsum-generator.html',
'/json-lint.html',
'/visual-difference.html',
'/notfound.html',
'/404.html'
];
self.addEventListener('install', (e) => {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then((cache) => {
console.log('[ServiceWorker] Caching app shell');
return cache.addAll(filesToCache);
})
);
});
self.addEventListener('activate', (e) => {
console.log('[ServiceWorker] Activate');
e.waitUntil(
caches.keys().then(function(keyList) {
return Promise.all(keyList.map((key) => {
if (key !== cacheName) {
console.log('[ServiceWorker] Removing old cache', key);
return caches.delete(key);
}
}));
})
);
});
self.addEventListener('fetch', (e) => {
console.log('[ServiceWorker] Fetch', e.request.url);
e.respondWith(
caches.match(e.request).then((response) => {
return response || fetch(e.request);
})
);
});
Then all files were not cached because of this. But if I remove the 404.html or rename it to other name the service worker will work fine and all files will be cached. It is also weird that in my local server the service worker works and caches 404.html but it fails in firebase.
Why 404.html causes uncaught error during service worker caching? How do I resolve this?
Cache.addAll() is an all or nothing API. If any response is not in the 200 HTTP status code range, nothing will be cached.
cache.addAll will reject if any of the resources fail to cache. This means the service worker will only install if all of the resources in cache.addAll have been cached.
Firebase returns 404 Not Found for the /404.html file.
An approach to resolve this is to have a file /notfound.html like you have and then return that in fetch when needed.
You cannot add 404 error html page into cache, if it returns 404 https status (as it should).
But still you can create 404 error page with service worker and use it for sites not in cache even if website is in offline mode.
Using catch() after fetch() request and create whole new Response
minimalistic example:
// self is ServiceWorkerGlobalScope
self.addEventListener( 'fetch', function ( /** #type {FetchEvent} */ event )
{
//const caches = ( event.target.caches );
event.respondWith(
caches.match( event.request ).then( ( /** #type {Response | undefined} */ response ) =>
{
if ( response ) {
return response;
}
return fetch( event.request.clone() ).then( ( /** #type {Response} */ response ) =>
{
//…
return response;
}
).catch( () =>
{
return new Response( '<h1>404 - Not found</h1><p>more HTML here …</p>', {
status: 404,
statusText: 'Not found',
headers: new Headers( {
'Content-Type': 'text/html'
} )
} );
} );
}
)
);
} );

How to validate a claim in id_token when using OpenIdConnect middleware?

I'm using Oath2 with Google authentication in my ASP.NET Core MVC app. I want to restrict logged in users to a certain G Suite domain which according to the docs is done using the "hd" (hosted domain) claim. I have it working but as it's authentication and I'm not familiar would like input. Am I doing this correctly? Is there a way to instead return a 401 status code instead of calling Fail() which results in a 500 error?
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddOpenIdConnect(o =>
{
var hostedDomain = new KeyValuePair<string, string>("hd", "mysite.com");
o.ClientId = "...";
o.ClientSecret = "...";
o.Authority = "https://accounts.google.com";
o.ResponseType = "id_token token";
o.Scope.Add("openid");
o.Scope.Add("email");
o.Scope.Add("profile");
o.GetClaimsFromUserInfoEndpoint = true;
o.SaveTokens = true;
o.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = (context) =>
{
// Add domain limiting using 'hd' or 'hosted domain' parameter
// Docs: https://developers.google.com/identity/protocols/OpenIDConnect#hd-param
//context.ProtocolMessage.SetParameter(hostedDomain.Key, "asdf.com");
// Set up redirect URLs
if (context.Request.Path != "/account/external")
{
context.Response.Redirect("/account/login");
context.HandleResponse();
}
return Task.FromResult(0);
},
OnTokenValidated = (c) =>
{
var hdClaim = c.SecurityToken.Claims.FirstOrDefault(claim => claim.Type == hostedDomain.Key);
if(hdClaim?.Value == null || hdClaim.Value != hostedDomain.Value)
{
// The claim is null or value is not the trusted google domain - do not authenticate!
c.Fail($"Invalid claim for '{hostedDomain.Key}'! User does not belong to trusted G Suite Domain");
}
return Task.FromResult(0);
}
};
});
services.AddMvc();
}
The above works when an incorrect or null hd claim is given which is done by logging in with an account not in the domain name in the hostedDomain.Value. I tried setting the c.Response.StatusCode = 401; but the user still logs in.
Another way to do this would be to use authorization.
You can set up a default authorization policy that requires the presence of the claim you test for. Then any caller that does not have the claim would get redirected to an access denied page. Something like:
services.AddAuthorization(o =>
{
o.AddPolicy("default", policy =>
{
policy.RequireAuthenticatedUser()
.RequireClaim("hd", "mysite.com");
});
});
services.AddMvc(o =>
{
o.Filters.Add(new AuthorizeFilter("default"));
});

ZF2 - ServiceManager & getServiceConfig problems - unable to fetch or create instance

I am coming across some problems when trying to use ZF2's authentication services. I have to following Module.php getServiceConfig function:
<?php
public function getServiceConfig()
{
return array(
'factories' => array(
'Auth\Model\CustomerTable' => function($sm) {
$tableGateway = $sm->get('CustomerTableGateway');
$table = new CustomerTable($tableGateway);
return $table;
},
'CustomerTableGateway' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Customer()); // prototype pattern implemented.
return new TableGateway('customer', $dbAdapter, null, $resultSetPrototype);
},
'Auth\Model\AuthStorage' => function($sm){
return new \Auth\Model\AuthStorage('jamietech');
},
'AuthService' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter,
'customer','username','password');
$authService = new AuthenticationService();
$authService->setAdapter($dbTableAuthAdapter);
$authService->setStorage($sm->get('Auth\Model\AuthStorage'));
return $authService;
},
),
);
}
The AuthStorage factory simply creates a new AuthStorage for us to keep track of the rememberMe function I have, and the AuthService factory creates a new Authentication Service for us. I can't see anything that I have done wrong but when running the following code in the AuthController.php:
<?php
public function loginAction()
{
//if already login, redirect to success page
if ($this->getAuthService()->hasIdentity()){
return $this->redirect()->toRoute('success');
}
$form = new LoginForm();
return array(
'form' => $form,
'messages' => $this->flashmessenger()->getMessages()
);
}
public function logoutAction()
{
$this->getSessionStorage()->forgetMe();
$this->getAuthService()->clearIdentity();
$this->flashmessenger()->addMessage("You have logged out successfully.");
return $this->redirect()->toRoute('auth', array('action'=>'login'));
}
PHPUnit encounters the following errors when running the PHPUnit command:
1: "testLoginActionCanBeAccessed - Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance of Zend\Db\Adapter\Adapter
1: "testLogoutActionCanBeAccessed - session_regenerate_id(): cannot regenerate session id - headers already sent.
And this error for both login and logout when the -process-isolation command is run:
"Serialization of closure is not allowed in: C;\Users\-----\AppData\Local\Temp\-----
If somebody could help that would be great. I am a ZF noob so try not to be too harsh.
EDIT: BTW THe global.php file includes the service_manager adapter factory illustrated in the ZF2 tutorial application.
Thank you!
Jamie Mclaughlan
did you check these:
autoload_classmap.php (for your module)
in your module.config.php
like this
service_manager' => array(
'aliases' => array(
'mymodule-ZendDbAdapter' => 'Zend\Db\Adapter\Adapter',
),
);
I hope it helps you to find the answer

Resources