Redirecting Controller through OWIN startup - asp.net-core

I am not a .net developer and got a task to apply SSO in an existing .net application.
I have added OWIN OpenIDConnect for SSO. My project is not MVC project. Now my call is going through Startup.cs at the iisexpress server startup time. But my login page (login.aspx) is being called with /login.aspx.
I want to call startup when I call submit button on login page. The call is not going through startup.
My ConfigureAuth method is
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(Microsoft.Owin.Security.Cookies.CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new Microsoft.Owin.Security.Cookies.CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/login.ashx")
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
Authority = "Authority",
ClientId = "ClientId",
RequireHttpsMetadata = false,
ClientSecret = "ClientSecret",
Scope = OpenIdConnectScope.OpenIdProfile,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
AuthenticationMode = AuthenticationMode.Active,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
RedirectToIdentityProvider = (context) =>
{
//I'm able to break into this method
return Task.FromResult(0);
},
MessageReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
SecurityTokenValidated = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthorizationCodeReceived = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
//doesn't seem to run this line
return Task.FromResult(0);
},
}
});
System.Console.WriteLine("After OpenIdconfiguration");
}
Please suggest how can I redirect the Controller call through startup class.
Thank you

The short answer is that most of the code will not be executed directly. These are events that are executed as part of the authentication process.
The longer answer is that you need to fill in the blanks on what needs to actually happen for each of these events. A little bit of information on OWIN might help.
I think of OWIN as a pipeline that leads to your application. The request goes through the pipeline before it reaches your application. When your application sends a response, this pipeline is used again. Along the way, there are components called "middleware" that can act on the request and on the response. Using the pipeline model, think of the middleware as valves in the pipeline. The middleware is executed in order on the way in, and in reverse order on the way out.
So back to your code. This line:
app.UseOpenIdConnectAuthentication(
Tells OWIN to use OpenIdConnect for authentication. You need to write the code for each of the events that are part of the OpenIdConnect process. The first one RedirectToIdentityProvider needs to redirect (probably with an http 302) to your identity provider, which could be something like ForgeRock or PingIdentity or IdentityServer. Each of those events needs the code written for them.
The whole topic of OpenIdConnect is a fairly broad and is more than a little complex, probably more than what I could realistically do inside an answer. But there are plenty of resources on the topic.
I hope that helps

Related

.net 5 Authentication Cookie not set

I have a Umbraco 9 .Net 5 AspNet Core project.
I'm trying to set an auth cookie. I've followed microsofts guide and got it working in a seperate project but when trying to implement it in my Umbraco project it fails. I'm not sure why but I guess the Umbraco 9 Configuration has a part in it.
I've got as far as getting User.Identity.IsAuthenticated = true in the same controller as I sign in but as soon as I redirect to another controller the Authentication status is false.
I also try to set the LoginPath option when configure the cookie but it still redirect to the default path (/Account/Login) so something here is no working either
My StartUp.cs looks like following
public void ConfigureServices(IServiceCollection services)
{
services.AddUmbraco(mEnvironment, mConfig)
.AddBackOffice()
.AddWebsite()
.AddComposers()
.Build();
services.AddDistributedMemoryCache();
//services.AddSession(options =>
//{
// options.IdleTimeout = TimeSpan.FromSeconds(10);
// options.Cookie.HttpOnly = true;
// options.Cookie.IsEssential = true;
//});
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/portal/"; //not working, still redirects to default
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseAuthorization();
//umbraco setup
app.UseUmbraco()
.WithMiddleware(u =>
{
u.UseBackOffice();
u.UseWebsite();
})
.WithEndpoints(u =>
{
u.UseInstallerEndpoints();
u.UseBackOfficeEndpoints();
u.UseWebsiteEndpoints();
});
//app.UseSession();
}
My Login controller action looks like follows:
public async Task<ActionResult> Login()
{
var claimsIdentity = new ClaimsIdentity(new List<Claim>
{
new Claim(UserClaimProperties.UserRole, MemberRole, ClaimValueTypes.String)
}, CookieAuthenticationDefaults.AuthenticationScheme);
var authProps = new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
IsPersistent = true,
AllowRefresh = true,
RedirectUri = "/"
};
await HttpContext.SignInAsync(
//CookieAuthenticationDefaults.AuthenticationScheme, //from MS-example but isAuth will be false using this
new ClaimsPrincipal(claimsIdentity),
authProps);
var isAuthenticated = User.Identity.IsAuthenticated;
return Redirect("/myview/");
}
If I set the Auth Scheme to "Cookies" in SignInAsync like it is in the microsoft example isAuthenticated will be false but without this I'll at least get it true here.
When redirected to the next action the User.Identity.IsAuthenticated is false.
Any suggestions why that is or why my LoginPath configuration wont work?
Edit: I don't want to create Umbraco members for each user that logs in. I just want to sign in a user to the context and be able to validate that the user is signed in by myself in my controllers.
Edit 2: I've try to catch the sign in event and got a breakpoint in that even. In my demo app(without umbraco) I'll get to the breakpoint in the one with Umbraco this breakpoint is never hit so. Is this because Umbraco probably override this or hijack the event?
Not sure why but after testing different Authentication schemes I got an error that the schemes I tested was not registered and I got a list of already registered schemes.
I thought that by doing this
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
options.LoginPath = "/portal/"; //not working, still redirects to default
});
I've registered the "Cookies" scheme.
One of the schemes listed as registered was "Identity.Application" and by using that one I could get the User identity from the context in my redirect controller.

OpenIdDict Degraded Mode with ClientCredentials flow

I'm following this blog about allowing OpenIDDict to wrap an alternative authentication provider but return a JWT token from OpenIDDict itself:
https://kevinchalet.com/2020/02/18/creating-an-openid-connect-server-proxy-with-openiddict-3-0-s-degraded-mode/
This is really about intercepting the Authorization Code flow rather than the Client Credentials flow, but it provides a good starting point.
Unfortunately it states that "we don't need to override the HandleTokenRequestContext", which is appropriate for the blog but not (as far as I know) for my use case.
I think I need to implement a custom HandleTokenRequestContext but when I do so, the code runs, no errors but the HTTP response is empty. No token is generated.
How should I properly intercept the Client Credentials flow so that I can call out to another provider to validate the credentials, get a result and include that in the custom claims that I need to add to the JWT?
Code below:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContext>(options =>
{
// Configure the context to use an in-memory store - probably not needed?
options.UseInMemoryDatabase(nameof(DbContext));
// Register the entity sets needed by OpenIddict.
options.UseOpenIddict();
});
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore()
.UseDbContext<DbContext>();
})
.AddServer(options =>
{
options.SetTokenEndpointUris("/connect/token");
options
//.AllowRefreshTokenFlow()
.AllowClientCredentialsFlow();
// Register the signing and encryption credentials.
// options.AddDevelopmentEncryptionCertificate()
// .AddDevelopmentSigningCertificate();
//Development only
options
.AddEphemeralEncryptionKey()
.AddEphemeralSigningKey()
.DisableAccessTokenEncryption();
// Register scopes (i.e. the modes we can operate in - there may be a better way to do this (different endpoints?)
options.RegisterScopes("normal", "registration");
//TODO: Include Quartz for cleaning up old tokens
options.UseAspNetCore()
.EnableTokenEndpointPassthrough();
options.EnableDegradedMode(); //Activates our custom handlers as the only authentication mechansim, otherwise the workflow attempt to invoke our handler *after* the default ones have already failed
//the request
options.AddEventHandler<ValidateTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
//TODO: Check that the client Id is known
if (!string.Equals(context.ClientId, "client-1", StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidClient,
description: "The specified 'client_id' doesn't match a known Client ID.");
return default;
}
return default;
}));
options.AddEventHandler<HandleTokenRequestContext>(builder =>
builder.UseInlineHandler(context =>
{
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, OpenIddictConstants.Claims.Name, OpenIddictConstants.Claims.Role);
identity.AddClaim(OpenIddictConstants.Claims.Subject, context.ClientId, OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken);
if (context.Request.Scope == "registration")
{
//TODO: Authenticate against BackOffice system to get it's token so we can add it as a claim
identity.AddClaim("backoffice_token", Guid.NewGuid().ToString(), OpenIddictConstants.Destinations.AccessToken);
}
else
{
//TODO: Authenticate against internal authentication database as normal
}
var cp = new ClaimsPrincipal(identity);
cp.SetScopes(context.Request.GetScopes());
context.Principal = cp;
//This doesn't work either
//context.SignIn(context.Principal);
//ERROR: When this exits the response is empty
return default;
}));
});
//.AddValidation(options =>
//{
// options.UseLocalServer();
// options.UseAspNetCore();
//});
services.AddControllers();
services.AddHostedService<CredentialLoader>();
}
In the end, I got this working with 3 changes:
In the HandleTokenRequestContext:
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
Not sure if this is essential or not. Then remove the
context.Principal = cp;
but re-add the
context.SignIn(context.Principal);

How do I acquire the right token for a Web API from within an Azure Function App or Javascript?

We have a web service which requires authentication before use. When you type in the URL of the Web Service directly in the browser, everything works fine. However, if you were to try and call this very same service from Javascript, it doesn't work because authentication has yet to happen.
I've tried calling getAccessTokenAsync (this is part of the OfficeJS libray) but ended up getting one of those 1300x errors. Also, since this call is still in Preview I would like to avoid it.
The code below gets invoked when you enter the URL to the webservice directly in the browser windows. You're authenticated and everything works fine.
I just don't know how to do the equivalent authentication from within an Azure Function App, or Javascript (from a Web-Add-In)
public partial class AuthStartup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
// This part is for web sso so web pages can consume the API without obtaining a token
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
// http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/
Notifications = new WsFederationAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
context.ProtocolMessage.Whr = "ourcompany.com";// similar effect to domain_hint from client so users never see the "choose account" prompt
return Task.FromResult(0);
}
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
// this part is needed so that cookie and token auth can coexist
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = new string[] { $"spn:{ConfigurationManager.AppSettings["ida:Audience"]}" }
}
});
// This part is for bearer token authentication
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
MetadataAddress = ConfigurationManager.AppSettings["ida:MetadataAddress"],
});
}
}

Azure mobile apps Custom + Facebook authentication with Xamarin.Forms

I'm working on a Xamarin Forms mobile app with .NET backend. I followed this guide and successfully set up custom authentications with one change in Startup.cs:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
Without "if (string.IsNullOrEmpty(settings.HostName))". Otherwise I am always getting unauthorized for all requests after login.
Server project:
Auth controller
public class ClubrAuthController : ApiController
{
private readonly ClubrContext dbContext;
private readonly ILoggerService loggerService;
public ClubrAuthController(ILoggerService loggerService)
{
this.loggerService = loggerService;
dbContext = new ClubrContext();
}
public async Task<IHttpActionResult> Post(LoginRequest loginRequest)
{
var user = await dbContext.Users.FirstOrDefaultAsync(x => x.Email == loginRequest.username);
if (user == null)
{
user = await CreateUser(loginRequest);
}
var token = GetAuthenticationTokenForUser(user.Email);
return Ok(new
{
authenticationToken = token.RawData,
user = new { userId = loginRequest.username }
});
}
private JwtSecurityToken GetAuthenticationTokenForUser(string userEmail)
{
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, userEmail)
};
var secretKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY");
var audience = Identifiers.Environment.ApiUrl;
var issuer = Identifiers.Environment.ApiUrl;
var token = AppServiceLoginHandler.CreateToken(
claims,
secretKey,
audience,
issuer,
TimeSpan.FromHours(24)
);
return token;
}
}
Startup.cs
ConfigureMobileAppAuth(app, config, container);
app.UseWebApi(config);
}
private void ConfigureMobileAppAuth(IAppBuilder app, HttpConfiguration config, IContainer container)
{
config.Routes.MapHttpRoute("ClubrAuth", ".auth/login/ClubrAuth", new { controller = "ClubrAuth" });
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = Environment.GetEnvironmentVariable("WEBSITE_AUTH_SIGNING_KEY"),
ValidAudiences = new[] { Identifiers.Environment.ApiUrl },
ValidIssuers = new[] { Identifiers.Environment.ApiUrl },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
Client project:
MobileServiceUser user = await MobileClient.LoginAsync(loginProvider, jtoken);
Additionally I configured Facebook provider in azure portal like described here.
But it works only when I comment out app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions(){...}); in Startup.cs.
What I am missing to make both types of authentication works at the same time?
Since you have App Service Authentication/Authorization enabled, that will already validate the token. It assumes things about your token structure, such as having the audience and issuer be the same as your app URL (as a default).
app.UseAppServiceAuthentication() will also validate the token, as it is meant for local development. So in your example, the token will be validated twice. Aside from the potential performance impact, this is generally fine. However, that means the tokens must pass validation on both layers, and I suspect that this is not the case, hence the error.
One way to check this is to inspect the tokens themselves. Set a breakpoint in your client app and grab the token you get from LoginAsync(), which will be part of that user object. Then head to a service like http://jwt.io to see what the token contents look like. I suspect that the Facebook token will have a different aud and iss claim than the Identifiers.Environment.ApiUrl you are configuring for app.UseAppServiceAuthentication(), while the custom token probably would match it since you're using that value in your first code snippet.
If that holds true, than you should be in a state where both tokens are failing. The Facebook token would pass the hosted validation but fail on the local middleware, while the custom token would fail the hosted validation but pass the local middleware.
The simplest solution here is to remove app.UseAppServiceAuthentication() when hosting in the cloud. You will also need to make sure that your call to CreateToken() uses the cloud-based URL as the audience and issuer.
For other folks that find this issue
The documentation for custom authentication can be found here.
A general overview of App Service Authentication / Authorization can be found here.
The code you reference is only for local deployments. For Azure deployments, you need to turn on App Service Authentication / Authorization - even if you don't configure an auth provider (which you wouldn't in the case of custom auth).
Check out Chapter 2 of my book - http://aka.ms/zumobook

Add id_token as claim AspNetCore OpenIdConnect middleware

I am trying to set IdTokenHint when sending the sign out request. In the previous Microsoft.Owin.Security.OpenIdConnect middleware I would be able to set the id_token as a claim in the SecurityTokenValidated method using the SecurityTokenValidated notification by doing something like this:
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
...
Notifications = new OpenIdConnectAuthenticationNotifications
{
//Perform claims transformation
SecurityTokenValidated = async notification =>
{
...
notification.AuthenticationTicket.Identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));
},
RedirectToIdentityProvider = async n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token").Value;
n.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
}
}
With the new middleware Microsoft.AspNetCore.Authentication.OpenIdConnect (in ASP.NET Core RC2) I am having trouble trying to accomplish the same thing. I am assuming I should tap into the Events like so.
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
...
Events = new OpenIdConnectEvents()
{
OnTokenValidated = context =>
{
...
context.SecurityToken.Payload.AddClaim(new Claim("id_token", context.ProtocolMessage.IdToken));
},
OnRedirectToIdentityProviderForSignOut = context =>
{
var idTokenHint = context.HttpContext.User.FindFirst("id_token").Value;
context.ProtocolMessage.IdTokenHint = idTokenHint;
}
}
}
The problem I'm seeing is that the claims do not remain on the SecurityToken and don't get set on the HttpContext.User. What am I missing?
Regarding your code above, at least in version 2.1 of ASP.NET Core, the ID token can be accessed via context.Properties.GetTokenValue(...) (rather than as a user claim).
And, as Brock Allen said in a comment to your question, the OpenIdConnectHandler will automatically include the idTokenHint on sign out. However, and this bit me for a few hours today, when the handler processes the sign-in callback, it will only save the tokens for later if OpenIdConnectOptions.SaveTokens is set to true. The default is false, i.e., the tokens are no longer available when you do the sign-out.
So, if SaveTokens is true, the handler will automatically include the idTokenHint on logout, and you can also manually access the id token via context.Properties.GetTokenValue(...).