Authentication in Asp.Net MVC and Angularjs - c#

I am trying to combine OpenId authentication and bearer token authentication through the use of IdentityServer.
My project is an Asp.net MVC project, with a web api component. I would like to use OpenId authentication for the standard MVC controllers, and bearer token authentication for the web api component.
My front end is written in Angularjs, and calls web api methods through the $http service to populate the UI.
I am having a problem setting up the authentication workflow.
My thoughts initially were to try to execute the following algorithm:
User requests some web api method from the server through an ajax call ($http)
App detects the user is not authenticated and bearer token is not present in request header, redirects to MVC controller method to retrieve the bearer token.
Since the user is not authenticated, the server will redirect to the IdentityServer default login page.
Once the user is logged in, the server automatically redirects back to the bearer token method to get a bearer token
The generated bearer token is somehow returned to the client.
However my algorithm breaks down at step 3. I am trying to implement step 2 by using an httpinterceptor to inspect outgoing request to determine if a bearer token is present.
app.factory('authInterceptorService', ['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
var authInterceptorServiceFactory = {};
var _request = function (config) {
config.headers = config.headers || {};
var authData = localStorageService.get('authorizationData');
if (authData) {
config.headers.Authorization = 'Bearer ' + authData.token;
}
else {
$location.path('/Authentication/BearerToken');
}
return config;
}
var _responseError = function (rejection) {
if (rejection.status === 401) {
$location.path('/Authentication/BearerToken');
}
return $q.reject(rejection);
}
authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
return authInterceptorServiceFactory;}]);
If the token is not present, I exit the SPA and redirect to the bearer token MVC method.
This will successfully redirect to the login page, however once I log in, I am returned a bearer token in JSON directly on the page, since I am now outside of the Ajax call.
So my question is
could you please provide me with alternate ideas (and an outline of an implementation) on how to combine these two modes of authentication into one workflow?
Perhaps there is a way to customize IdentityServer to do what I want?
My basic requirement is that an unauthenticated user on my SPA angularjs app is redirected to the default IdentityServer login page, and once logged in, that the initial request be fulfilled.
Thanks in advance!
Just in case you are curious, my IdentityServer setup is as follows. The authentication service is in a separate project from the web application. Each has it's own Startup.cs file.
The MVC Startup.cs file
public class Startup
{
public void Configuration(IAppBuilder app)
{
AntiForgeryConfig.UniqueClaimTypeIdentifier = Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
});
var openIdConfig = new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
ClientId = "baseballStats",
Scope = "openid profile roles baseballStatsApi",
RedirectUri = "https://localhost:44300/",
ResponseType = "id_token token",
SignInAsAuthenticationType = "Cookies",
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async n =>
{
var userInfoClient = new UserInfoClient(
new Uri(n.Options.Authority + "/connect/userinfo"),
n.ProtocolMessage.AccessToken);
var userInfo = await userInfoClient.GetAsync();
// create new identity and set name and role claim type
var nid = new ClaimsIdentity(
n.AuthenticationTicket.Identity.AuthenticationType,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.GivenName,
Thinktecture.IdentityServer.Core.Constants.ClaimTypes.Role);
userInfo.Claims.ToList().ForEach(c => nid.AddClaim(new Claim(c.Item1, c.Item2)));
// keep the id_token for logout
nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
// add access token for sample API
nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken));
// keep track of access token expiration
nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString()));
// add some other app specific claim
nid.AddClaim(new Claim("app_specific", "some data"));
n.AuthenticationTicket = new AuthenticationTicket(
nid,
n.AuthenticationTicket.Properties);
}
}
};
app.UseOpenIdConnectAuthentication(openIdConfig);
app.UseResourceAuthorization(new AuthorizationManager());
app.Map("/api", inner =>
{
var bearerTokenOptions = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44301/identity",
RequiredScopes = new[] { "baseballStatsApi" }
};
inner.UseIdentityServerBearerTokenAuthentication(bearerTokenOptions);
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
inner.UseWebApi(config);
});
}
}
The IdentityServer Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var policy = new System.Web.Cors.CorsPolicy
{
AllowAnyOrigin = true,
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
//policy.ExposedHeaders.Add("location: http://location.com");
policy.Headers.Add("location: testing");
app.UseCors(new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context => Task.FromResult(policy)
}
});
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = InMemoryFactory.Create(
users: Users.Get(),
clients: Clients.Get(),
scopes: Scopes.Get())
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\Configuration\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
EDIT
I have made some progress in redirecting to the Login page. The issue I was having is that the initial call to the web api was being redirected by IdentityServer with a status code of 302. This meant that I could not get access to the Location header which contains the url I want to redirect to. So instead, I added some owin middleware to check the status code, and if a 302, return a 401 instead (I need to also check that the call is Ajax but have not yet implemented this).
app.Use(async (environment, next) =>
{
await next();
if (environment.Response.StatusCode == 302)
{
environment.Response.StatusCode = 401;
}
});
This then gives me access to the Location header on the client side, and I can redirect, like so:
getPlayerList: function (queryParameters) {
var deferred = $q.defer();
$http.post('api/pitchingstats/GetFilteredPlayers', {
skip: queryParameters.skip,
take: queryParameters.take,
orderby: queryParameters.orderby,
sortdirection: queryParameters.sortdirection,
filter: queryParameters.filter
}).success(function (data, status, headers, config) {
if (status === 401) {
window.location = headers().location;
}
deferred.resolve(data);
}).error(function (data, status, headers, config) {
deferred.reject(status);
});
I know it's a hack, but it's the only way I can make my approach work currently. By all means feel free to suggest other methods.

Related

Blazor WASM: Add JWT auth with custom API

I have built an API that controls some smart home stuff. To prevent the whole internet from doing so, I added authentication using JWT / Bearer. The API contains endpoints for the smart home stuff aswell as some user management:
API endpoints for users
The login will return a JWT token if credentials were valid. It is also built using .NET 6:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(x =>
{
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
};
});
Login Controller:
[HttpPost]
public async Task<IActionResult> Login([FromBody] UserLogin login)
{
var user = await _userService.GetUser(login.Username);
if (user is not null && _userService.IsPasswordCorrect(user, login.Password))
{
var tokens = await _userService.GetJwtAndRefreshToken(user);
return Ok(new LoginResponse { JWT = tokens.Jwt, RefreshToken = tokens.Refreshtoken });
}
return Unauthorized("Wrong username or password!");
}
Now I am trying to build a frontend for this app using blazor. When creating the app, i used the option "individual user accounts" for authentication. It is documented here: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/standalone-with-authentication-library?view=aspnetcore-6.0&tabs=visual-studio
This created the following in the blazow WASM app:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddOidcAuthentication(options =>
{
// Configure your authentication provider options here.
// For more information, see https://aka.ms/blazor-standalone-auth
builder.Configuration.Bind("Local", options.ProviderOptions);
});
await builder.Build().RunAsync();
appsettings.json looks like this:
{
"Local": {
"Authority": "https://localhost:7110/login",
"ClientId": "33333333-3333-3333-33333333333333333"
}
}
I changed the Authority to my login api url, but doesn't seem to be enough.
Clicking on the login button that was added by default fires this request:
Request
Is there a simple way to use the MS Authorization framework with my custom api?
I spent great amount of time on this. These are my notes from it. Note that I am using IdentityServer. Probably a lot of stuff will be different for you. But it should at least guide you what to check.
It works (for me), but best-practise is not garantee.
My API address is on port 5001, Client is on port 5101
For Client project
Change HttpClient address in Client. Change Http MessageHandler. Change address for public client
var clientBaseAddress = new Uri(builder.Configuration["apiurl"] ?? throw new ArgumentNullException("apirul is null (reading from config file)"));
builder.Services.AddHttpClient("BlazorApp6.ServerAPI", client =>client.BaseAddress = clientBaseAddress)
.AddHttpMessageHandler(sp =>
{//this is need when api is separated. https://code-maze.com/using-access-token-with-blazor-webassembly-httpclient/
var handler = sp.GetService<AuthorizationMessageHandler>()!
.ConfigureHandler(
authorizedUrls: new[] { builder.Configuration["HttpMessageHandlerAuthorizedUrls"] },
scopes: new[] { "BlazorApp6.ServerAPI" }
);
return handler;
});
builder.Services.AddHttpClient<PublicClient>(client => client.BaseAddress = clientBaseAddress);
Add HttpMessageHandlerAuthorizedUrls apiurl to appsettings (example for developement):
"apiurl": "https://localhost:5001",
"HttpMessageHandlerAuthorizedUrls": "https://localhost:5001",
Program.cs AddApiAuthorization is different (set opt.ProviderOptions.ConfigurationEndpoint)
builder.Services.AddApiAuthorization(
//this line is only when address of api consumer is different
opt => opt.ProviderOptions.ConfigurationEndpoint = builder.Configuration["ApiAuthorizationConfigurationEndpoint"]
).AddAccountClaimsPrincipalFactory<CustomUserFactory>();
Add ApiAuthorizationConfigurationEndpoint to appsettings
"ApiAuthorizationConfigurationEndpoint": "https://localhost:5001/_configuration/BlazorApp6.Client"
Change launchSetting to different port
"applicationUrl": "https://localhost:5101;http://localhost:5100",
For api project
Add cors to client app
string developmentCorsPolicy = "dev_cors";
services.AddCors(opt =>
{
opt.AddPolicy(name: developmentCorsPolicy, builder =>
{
builder.WithOrigins("https://localhost:5101", "https://localhost:5201")
.WithMethods("GET", "POST", "PUT", "DELETE")
.AllowAnyHeader();
});
});
//...
if (app.Environment.IsDevelopment())
app.UseCors(developmentCorsPolicy);
There is probably some need to add cors for identiy server, but it works without it.
in case it is needed:
services.AddSingleton<ICorsPolicyService>((container) =>
{
var logger = container.GetRequiredService<ILogger<DefaultCorsPolicyService>>();
return new DefaultCorsPolicyService(logger)
{
AllowAll = true
};
});
Change appsettings IdentityServer section to have some info about client.
This info is obtained in OidcController with requests starting _configuration:
"IdentityServer": {
"Clients": {
"BlazorApp6.Client": {
"Profile": "SPA",
"LogoutUri": "https://localhost:5101/authentication/logout-callback",
"RedirectUri": "https://localhost:5101/authentication/login-callback"
},
},
"Key": {
"Type": "Development"
} }
Note that Profile has changed to SPA (instead of IdentityServerSPA, which means hosted)

Need help adjusting ASP.NET that uses Azure AD to also allow SSO from third party

We have a ASP.NET application that uses Microsoft's common login to log in the users, then it redirects back to our web application (in Azure). The authentication is connected to Azure Active Directories. It is a multi-tenant application with multiple Azure ADs. When Microsoft redirects back to our site, we use the information to create a cookie that is used for the web calls. In addition, the call back returns a user code that we use to get a token. This is used as authentication against our API controllers.
This has been working for a long time. Now, however, we need to integrate with another 3rd party portal that will launch our product. They will be using SAML for SSO. They are not integrated with Azure AD. So, the idea is that we validate the users via the SAML assertions. That will contain the username that we then want to "log in" with.
I can create the cookie off of this information and that works fine with our web controller calls. However, since I'm not getting a callback from Azure AD, I don't have the token for the API calls. I have tried to call Azure AD to authenticate the applications, but that does seem to satisfy the API Controller's authorization. Specifically, RequestContext.Principal.Identity doesn't seem to be set with this pattern.
I have set the cookie authentication with this code:
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieOptions = new CookieAuthenticationOptions
{
ExpireTimeSpan = TimeSpan.FromDays(14),
SlidingExpiration = true,
LoginPath = new PathString("/home/login"),
AuthenticationType = "ApplicationCookie",
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true,
CookieSecure = CookieSecureOption.Always,
CookieSameSite = SameSiteMode.Lax,
};
// Forms/Cookie Authentication
app.UseCookieAuthentication(cookieOptions);
And I left the bearer token authentication code like this:
// Bearer Token Authentication used for API access
BearerAuthenticationOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = <application tenant id>,
AuthenticationType = OAuthDefaults.AuthenticationType,
// Disable Issuer validation. We'll validate the Isuuer in the ClaimsAuthorizationFilter.
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = <resource id>,
ValidateIssuer = false
},
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(BearerAuthenticationOptions);
The code that handles the Azure AD auth (that the SAML login should replace) is:
var openIdConnectOptions = new OpenIdConnectAuthenticationOptions { ClientId = <ClientId>, Authority = "https://login.windows.net/common/", // setting this to false uses the cookie expiration instead UseTokenLifetime = false, TokenValidationParameters = new TokenValidationParameters { // we'll validate Issuer on the SecurityTokenValidated notification below ValidateIssuer = false },
Notifications = new OpenIdConnectAuthenticationNotifications { // An AAD auth token is validated and we have a Claims Identity SecurityTokenValidated = context => { ... additional validation is performed here...
return Task.FromResult(0); },
//the user has just signed in to the external auth provider (AAD) and then were redirected here // with an access code that we can use to turn around and acquire an auth token AuthorizationCodeReceived = context => { var code = context.Code; var identity = context.AuthenticationTicket.Identity;
var appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase; // causes the retreived API token to be cached for later use TokenService.GetUserLevelTokenFromAccessCode(new HttpUserSessionWithClaimsId(identity), code, <ApiResourceId>, new Uri(appBaseUrl));
return Task.FromResult(0); }, // We are about to redirect to the identity provider (AAD) RedirectToIdentityProvider = context => { // This ensures that the address used for sign in and sign out is picked up dynamically from the request // Remember that the base URL of the address used here must be provisioned in Azure AD beforehand. var appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase; context.ProtocolMessage.RedirectUri = appBaseUrl + "/"; context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
context.HandleResponse();
return Task.FromResult(0); }, // Something went wrong during this auth process AuthenticationFailed = context => { if (context.Exception is Microsoft.IdentityModel.Protocols.OpenIdConnect
.OpenIdConnectProtocolInvalidNonceException) {
//This is a message we can't do anything about, so we want to ignore it.
Log.Info("AuthenticationFailed in OpenIdConnectAuthentication middleware", context.Exception); } else {
Log.Error("AuthenticationFailed in OpenIdConnectAuthentication middleware",
context.Exception); }
// IDX10205 == Tenant validation failed var message = (context.Exception.Message.StartsWith("IDX10205"))
? InvalidTenantMessage
: GenericErrorMessage;
context.OwinContext.Response.Redirect("/?Error=" + Uri.EscapeDataString(message)); context.HandleResponse(); // Suppress the exception return Task.FromResult(0); }, MessageReceived = context => { if (!string.IsNullOrWhiteSpace(context.ProtocolMessage.Error)) {
// AADSTS65004 == user did not grant access in OAuth flow
Log.Error("MessageReceived containing error in OpenIdConnectAuthentication middleware. \nError: {0}\nDescription: {1}"
.FormatWith(context.ProtocolMessage.Error, context.ProtocolMessage.ErrorDescription));
//context.OwinContext.Response.Redirect("/");
//context.HandleResponse(); // Suppress the exception } return Task.FromResult(0); } } };
app.UseOpenIdConnectAuthentication(openIdConnectOptions);
Any help would be greatly appreciated.
Turns out the value I put for AuthenticationType in the cookie was causing the disconnect. Once I fixed that, the data came through. So closing his question.
Thanks.

Owin Authentication In MVC and Web Api

I'm trying to use the same authentication between the MVC controllers and the Web Api controllers. The Web api is in the same project, just in an /Controllers/API/ folder.
I can't seem to figure out how to authenticate using OWIN, when I logged in through MVC and created a claim and a cookie like the example below.
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name,"Admin"),
new Claim(ClaimTypes.Role,"Administrator")
, "ApplicationCookie");
var ctx = Request.GetOwinContext();
var authManager = ctx.Authentication;
authManager.SignIn(identity);
return RedirectToAction("Index", "Home", null);
}
Everything works fine in the MVC controllers, but I can't use the [Authorize(Roles="Administrator"] attribute on my web API controller and have it work correctly. It always lets me through regardless.
Thanks
EDIT: Only way I've been able to combat this is having a static class and property store the IPrincipal and then when Overriding the Authorize Attribute, look for that property and check if the Role is there that way. Which im not sure if that is a good idea or not?
Where is your authentication code written ? MVC Controller or Web API Controller ? I would recommend to have it in your web API controller that way you can later use it for any other application (SPA or any other web application).You need to build a Authorization server/Resource Server model (sorry for my english wasn't sure how to frame this sentence). In your case Web API being both and MVC site being a resource server.
Below is a sample for JWT + Cookie middleware
Build a authorization server using JWT with WEB API and ASP.Net Identity as explained here http://bitoftech.net/2015/02/16/implement-oauth-json-web-tokens-authentication-in-asp-net-web-api-and-identity-2/
once you do that your webAPIs startup.cs will look like below
/// Configures cookie auth for web apps and JWT for SPA,Mobile apps
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context, user manager and role manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
//Cookie for old school MVC application
var cookieOptions = new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true, // JavaScript should use the Bearer
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/api/Account/Login"),
CookieName = "AuthCookie"
};
// Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
app.UseCookieAuthentication(new CookieAuthenticationOptions());
OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(30),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["JWTPath"])
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
You can find CustomOAuthProvider,CustomJwtFormat classes here https://github.com/tjoudeh/AspNetIdentity.WebApi/tree/master/AspNetIdentity.WebApi/Providers
In your MVC app add below in startup.cs
public void Configuration(IAppBuilder app)
{
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = ConfigurationManager.AppSettings["AuthIssuer"];
string audienceid = ConfigurationManager.AppSettings["AudienceId"];
byte[] audiencesecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["AudienceSecret"]);
app.UseCookieAuthentication(new CookieAuthenticationOptions { CookieName = "AuthCookie" , AuthenticationType=DefaultAuthenticationTypes.ApplicationCookie });
//// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Passive,
AuthenticationType = "JWT",
AllowedAudiences = new[] { audienceid },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audiencesecret)
}
});
}
In your MVC controller when you receive the token de-serialize it and generate a cookie from the acceSs token
AccessClaims claimsToken = new AccessClaims();
claimsToken = JsonConvert.DeserializeObject<AccessClaims>(response.Content);
claimsToken.Cookie = response.Cookies[0].Value;
Request.Headers.Add("Authorization", "bearer " + claimsToken.access_token);
var ctx = Request.GetOwinContext();
var authenticateResult = await ctx.Authentication.AuthenticateAsync("JWT");
ctx.Authentication.SignOut("JWT");
var applicationCookieIdentity = new ClaimsIdentity(authenticateResult.Identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
ctx.Authentication.SignIn(applicationCookieIdentity);
With this a cookie will be created and [Authorize] attribute in MVC Site and WebAPI will honor this cookie.

How to write OAuth2 Web API Client in Asp.net MVC

We have developed a set of Web APIs (REST) which are protected by an Authorization server. The Authorization server has issued the client id and client secret. These can be used to obtain an access token. A valid token can be used on subsequent calls to resource servers (REST APIs).
I want to write an web based (Asp.net MVC 5) client that will consume the APIs. Is there a nuget package I can download that will help me to implement the client OAuth2 flow? Can anyone direct me to a good example on client implementation of OAuth2 flow (written in asp.net MVC)?
Update
I was able to get access token using the code block below, but what I want is a "client credentials" oauth 2 flow where I don't have to enter login and passwords. The code I have now is:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType("ClientCookie");
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = "ClientCookie",
CookieName = CookieAuthenticationDefaults.CookiePrefix + "ClientCookie",
ExpireTimeSpan = TimeSpan.FromMinutes(5)
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AuthenticationType = OpenIdConnectAuthenticationDefaults.AuthenticationType,
SignInAsAuthenticationType = app.GetDefaultSignInAsAuthenticationType(),
ClientId = ConfigurationManager.AppSettings["AuthServer:ClientId"],
ClientSecret = ConfigurationManager.AppSettings["AuthServer:ClientSecret"],
RedirectUri = ConfigurationManager.AppSettings["AuthServer:RedirectUrl"],
Configuration = new OpenIdConnectConfiguration
{
AuthorizationEndpoint = "https://identityserver.com/oauth2/authorize",
TokenEndpoint = "https://identityserver.com/oauth2/token"
},
//ResponseType = "client_credentials", // Doesn't work
ResponseType = "token",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = notification =>
{
if (string.Equals(notification.ProtocolMessage.Error, "access_denied", StringComparison.Ordinal))
{
notification.HandleResponse();
notification.Response.Redirect("/");
}
return Task.FromResult<object>(null);
},
AuthorizationCodeReceived = async notification =>
{
using (var client = new HttpClient())
{
//var configuration = await notification.Options.ConfigurationManager.GetConfigurationAsync(notification.Request.CallCancelled);
String tokenEndPoint = "https://identityserver.com/oauth2/token";
//var request = new HttpRequestMessage(HttpMethod.Post, configuration.TokenEndpoint);
var request = new HttpRequestMessage(HttpMethod.Post, tokenEndPoint);
request.Content = new FormUrlEncodedContent(new Dictionary<string, string> {
{ OpenIdConnectParameterNames.ClientId, notification.Options.ClientId },
{ OpenIdConnectParameterNames.ClientSecret, notification.Options.ClientSecret },
{ OpenIdConnectParameterNames.Code, notification.ProtocolMessage.Code },
{ OpenIdConnectParameterNames.GrantType, "authorization_code" },
{ OpenIdConnectParameterNames.RedirectUri, notification.Options.RedirectUri }
});
var response = await client.SendAsync(request, notification.Request.CallCancelled);
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
// Add the access token to the returned ClaimsIdentity to make it easier to retrieve.
notification.AuthenticationTicket.Identity.AddClaim(new Claim(
type: OpenIdConnectParameterNames.AccessToken,
value: payload.Value<string>(OpenIdConnectParameterNames.AccessToken)));
}
}
}
});
}
}
To support the client credentials grant type, your best option is probably to directly use HttpClient:
var request = new HttpRequestMessage(HttpMethod.Post, "http://server.com/token");
request.Content = new FormUrlEncodedContent(new Dictionary<string, string> {
{ "client_id", "your client_id" },
{ "client_secret", "your client_secret" },
{ "grant_type", "client_credentials" }
});
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var token = payload.Value<string>("access_token");
For interactive flows (like the authorization code flow), there are two better approaches:
If your authorization server supports OpenID Connect (which is based on OAuth2), you can simply use the OpenID Connect middleware for OWIN/Katana 3 developed by Microsoft: https://www.nuget.org/packages/Microsoft.Owin.Security.OpenIdConnect/
If OpenID Connect is not supported by your authorization server, one option is to create your own OAuth2 client middleware. You can take a look at the last part of this SO answer for more information: Registering Web API 2 external logins from multiple API clients with OWIN Identity

Oauth authentication with owin & Nancy

Following this guide for external auth using MVC 5 on Owin - External login providers with owinkatana.
I have added the following to my Owin Nancy application
Startup.cs -
app.Properties["Microsoft.Owin.Security.Constants.DefaultSignInAsAuthenticationType"] = "ExternalCookie";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ExternalCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
});
app.UseTwitterAuthentication(new TwitterAuthenticationOptions
{
ConsumerKey = "mykey",
ConsumerSecret = "mypass"
});
LoginModule.cs (nancy module)
Post["ExternalLogin"] = _ =>
{
var provider = Request.Form.name;
var auth = Context.GetAuthenticationManager();
auth.Challenge(new AuthenticationProperties
{
RedirectUri = String.Format("/?provder={0}", provider)
}, provider);
return HttpStatusCode.Unauthorized;
};
Now at the challenge point here nothing happens whatsoever. It just shows a blank page with the Url of the redirect. I have confirmed that I can get it to work following the example in MVC.
Does anyone know the correct Nancy code for this section?
I'll expand on a comment I was about to leave and just make it an answer (even though you moved away from Nancy it seems). I asked a similar question, and was pointed to the following code example on github:
https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/tree/dev/samples/Nancy/Nancy.Client
Assuming you have your OIDC wired up properly in Startup.cs, the following code is what I needed to get Nancy module to trigger the authentication on my signin/signout routes:
namespace Nancy.Client.Modules {
public class AuthenticationModule : NancyModule {
public AuthenticationModule() {
Get["/signin"] = parameters => {
var manager = Context.GetAuthenticationManager();
if (manager == null) {
throw new NotSupportedException("An OWIN authentication manager cannot be extracted from NancyContext");
}
var properties = new AuthenticationProperties {
RedirectUri = "/"
};
// Instruct the OIDC client middleware to redirect the user agent to the identity provider.
// Note: the authenticationType parameter must match the value configured in Startup.cs
manager.Challenge(properties, OpenIdConnectAuthenticationDefaults.AuthenticationType);
return HttpStatusCode.Unauthorized;
};
Get["/signout"] = Post["/signout"] = parameters => {
var manager = Context.GetAuthenticationManager();
if (manager == null) {
throw new NotSupportedException("An OWIN authentication manager cannot be extracted from NancyContext");
}
// Instruct the cookies middleware to delete the local cookie created when the user agent
// is redirected from the identity provider after a successful authorization flow.
manager.SignOut("ClientCookie");
// Instruct the OpenID Connect middleware to redirect
// the user agent to the identity provider to sign out.
manager.SignOut(OpenIdConnectAuthenticationDefaults.AuthenticationType);
return HttpStatusCode.OK;
};
}
}
}
Code source: https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Nancy/Nancy.Client/Modules/AuthenticationModule.cs
Hope that helps!

Categories