Simple IdentityServer3/OpenIdConnect solution not working - HTTP 401.0 - Unauthorized - asp.net-mvc

I am new to IdentityServer and OpenId Connect. I am working with .NET Framework 6 and IdentityServer3 and OpenId Connect. I have been through the three walk throughs in the Overview section of the IdentityServer3 documentation - with console client, MVC client, and JavaScript client - and have those working solutions as a model. I am now trying to build my own authentication service with a simple MVC client for test purposes. It's not working. The symptom of its not working is that, the MVC client About page, which is secured with the [Authorize] attribute in the Home controller reports HTTP 401.0 - Unauthorized without ever redirecting the user to the login page. I've reviewed the network traffic in the Chrome developer tools and see the initial requests for /Home/About returning HTTP 401, never the expected HTTP 302 Redirect.
My IdentityServer3 configuration looks like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
// TODO: Replace use of InMemoryUsers with custom Orvis UserService
app.UseIdentityServer(new IdentityServerOptions
{
SiteName = "MyClient Authentication Service",
SigningCertificate = LoadCertificate(),
Factory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get().ToList()),
RequireSsl = true
});
}
X509Certificate2 LoadCertificate()
{
// TODO: Get real signing certificate?
return new X509Certificate2(string.Format(#"{0}\bin\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
The GetClients and GetScopes code looks like this:
public static IEnumerable<Client> Get()
{
// TODO: Replace hard-coded implementation of Clients.Get() with a configuration-driven implementation
return new List<Client>
{
new Client
{
Enabled = true,
ClientId = "mvc-sample",
ClientName = "MVC Sample Client",
RequireConsent = false,
Flow = Flows.Implicit,
RedirectUris = new List<string> { "http:/localhost:37320" },
AllowedCorsOrigins = new List<string> { "http://localhost:37320/" },
AllowedScopes = new List<string> { "orvis-services", "orvis-shopper-service" }
}
};
}
public static IEnumerable<Scope> Get()
{
return new List<Scope>
{
new Scope
{
Name = "all-services",
DisplayName = "All Services",
Description = "Access to all microservices",
Type = ScopeType.Resource
},
new Scope
{
Name = "shopper-service",
DisplayName = "Shopper Service",
Description = "Access to the Shopper microservice",
Type = ScopeType.Resource
}
};
}
And my client configuration looks like this:
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = "https://localhost:44332",
ClientId = "mvc-sample",
RedirectUri = "http://localhost:37320",
ResponseType = "id_token",
SignInAsAuthenticationType = "Cookies"
});
}
So, very similar to the IdentityServer3 "Getting Started: MVC Authentication and Web APIs" walk through. One difference is that the walk through has the client and server code in the same project and I've split them out into separate projects.
Since the dev tools network tab shows the request to /Home/About with a 401.0 response instead of the expected 302 response, I suspect there's something wrong with the cilent setup. But, beyond the browser dev tools, I'm not sure where to look to get at the underlying details.
Any suggestions appreciated.

The problem was that the IdentityServer3 walk through that I was using as a model had the IdentityServer and the MVC client in one project. When I created them separately, I added the Microsoft.Owin.Host.Systemweb and IdentityServer3 packages to the IdentityServer project and the Microsoft.Owin.Security.Cookies and Microsoft.Owin.Security.OpenIdConnect packages to the MVC client project. It turns out that the MVC client project also requires the Microsoft.Owin.Host.Systemweb package. without it, the Startup.Configuration() method is never called.

Related

Why do I get 'Sorry, there was an error' when trying to get Authentication code from Identity server 4?

I'm trying to implement an authorization code flow using Identity Server 4 but when I attempt to generate my authorization code all I get is a generic error page with nothing but the message 'Sorry, there was an error'. (Title is 'Error').
I get this whether I'm using Postman or programmatically submitting a GET request with the required parameters.
To run my test, in Visual studio I start the Identity server and my API server. I start my MVC site to log in as the test user if needed and to make the callback url available. I then press the 'Get New Access Token' button in Postman. The result is that generic error.
I understand that when I programmatically submit the GET request the response should be the Auth Code which would be ideal but at this point I just want to successfully authenticate the client.
Can anyone see anything I might be missing?
In Postman my parameters are as follows in this image:
My Client is set up as follows:
new Client
{
ClientId = "mvc",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code ,
// where to redirect to after login
RedirectUris = { "https://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1",
IdentityServerConstants.StandardScopes.Email
}
}
My test user is this one that comes with the Identity Server Github code:
new TestUser
{
SubjectId = "88421113",
Username = "bob",
Password = "bob",
Claims =
{
new Claim(JwtClaimTypes.Name, "Bob Smith"),
new Claim(JwtClaimTypes.GivenName, "Bob"),
new Claim(JwtClaimTypes.FamilyName, "Smith"),
new Claim(JwtClaimTypes.Email, "BobSmith#email.com"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
new Claim(JwtClaimTypes.Address, JsonSerializer.Serialize(address), IdentityServerConstants.ClaimValueTypes.Json)
}
}
My Startup for Identity Server:
public class Startup
{
public IWebHostEnvironment Environment { get; }
public Startup(IWebHostEnvironment environment)
{
Environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
// uncomment, if you want to add an MVC-based UI
services.AddControllersWithViews();
services.AddAuthentication()
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = "<insert here>";
options.ClientSecret = "<insert here>";
});
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication (options =>
{
options.Authority = "https://localhost:5001";
options.ApiName = "testapis";
});
var builder = services.AddIdentityServer(options =>
{
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddDeveloperSigningCredential() //This is for dev only scenarios when you don’t have a certificate to use.
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users)
.AddCustomTokenRequestValidator<CustomTokenRequestValidator>();
// not recommended for production - you need to store your key material somewhere secure
builder.AddDeveloperSigningCredential();
}
public void Configure(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
//// uncomment if you want to add MVC
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
//// uncomment, if you want to add MVC
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
}
One problem is that you should always ask for the openid scope, not just ap1.
I've finally been able to get my access tokens and from what I can tell, when the AllowedGrantType on the target client is 'ClientCredentials', you cannot request the openid or profile scopes. For my test client I had added those two scopes to the AllowedScopes list but always failed to get my auth code. If I only request an ApiScope it's fine. If the scope is an IdentityResource it will not work.
This is fine as my project requires that I implement the Code grantType which I've got working to a degree.

Azure B2C authentication redirects locally, but not when hosted on Azure

I'm new to Azure AD B2C. I'm trying to set up azure B2C authentication for an MVC application.
The login works fine locally, but when it's not working on server.
The application is hosted on Azure AD.
I don't know if I missed something!! Can someone please help?
private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string policy)
{
return new OpenIdConnectAuthenticationOptions
{
// set the authentication type to the id of the policy
MetadataAddress = metaDataAddress,
AuthenticationType = policy,
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = clientId,
//ClientSecret = clientSecret,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed,
},
Scope = "openid",
ResponseType = "id_token",
// used for displaying the user's name in the navigation bar.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
SaveSigninToken = true
}
};
}
When it is deployed on to the server, after sign in, it is not returning to the application. Instead the page seems to blink and in between I can see something displayed as "As part of the authentication process the page is displayed several times, please click the button to continue"..
Startup.cs file seems alright to me.Couple things to cross check:
redirectUri matches with the website
Here is my signup and sign in action method:
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignInPolicyId);
}
}
public void SignUp()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties() { RedirectUri = "/" }, Startup.SignUpPolicyId);
}
}
I have followed below github repo and it worked for me. Try this and see if it works
https://github.com/tjoudeh/Azure-Active-Directory-B2C/tree/master/AADB2C.WebClientMvc/Controllers
Reference:
http://bitoftech.net/2016/08/31/integrate-azure-ad-b2c-asp-net-mvc-web-app/
Please provide the detailed error with entire code base will try to reproduce at my end.

IdentityServer3 returns invalid_scope for authorize endpoint

I have and IdentityServer3 installed on my ASP.NET MVC website and a client (a NopCommerce plugin) defined as below (urls are just for test):
new Client
{
ClientName = "Deep Stores",
ClientId = "deepstores",
ClientSecrets = new List<Secret>
{
new Secret("0B0A3FD3-F30C-428C-B1A3-6E570103614D".Sha256())
},
AccessTokenType = AccessTokenType.Reference,
RedirectUris = new List<string>
{
"http://localhost:2030/plugins/ExternalAuthDeepStudies/logincallback"
},
Flow = Flows.AuthorizationCode,
AllowAccessToAllScopes = true,
AllowedScopes = StandardScopes.All.Select(x => x.Name).ToList()
}
When trying to get authorization token with a url like below, It works for the first time (login appears and grant page blah blah) but for the rest of requests it returns error=invalid_scope for my callback!
request to authorize endpoint :
https://127.0.0.1/core/connect/authorize?client_id=deepstores&redirect_uri=http%3A%2F%2Flocalhost%3A2030%2Fplugins%2FExternalAuthDeepStudies%2Flogincallback&scope=profile&response_type=code
what happens? and why do i get this error?
scopes are defined as :
public static IEnumerable<Scope> Get()
{
return new[]
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Email,
StandardScopes.Address,
StandardScopes.Phone,
StandardScopes.OfflineAccess,
StandardScopes.RolesAlwaysInclude,
StandardScopes.AllClaims,
};
}
Note that I use EntityFramework and all my staff are stored in database. plus that I use ASP.NET Identity for managing users and roles.

Use web api cookie for mvc cookie

I'm making a web application by using Web API 2 and MVC 5.
My app has api :
api/account/login, which is used for checking posted information and throw status 200 when an account is granted to access application.
Also, I have one view : /Home/Index which is only available to authenticated client.
Now, my approach is :
Call api/account/login, receive the cookie thrown from that api.
Attach thrown back cookie to browser.
When user access /Home/Index, view is available for him/her.
My questions are :
- Is my approach possible ?
- How can I encrypt my cookie in Web API 2 like MVC 5 does to its cookie ?
Thank you,
The best way to achieve this to have a authorization server (a webAPI generating a token) and token consumption middle ware in your MVC project.IdentityServer https://github.com/IdentityServer/IdentityServer3 should help. However I have done this as below
Built an 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
Write a consumption logic (i.e. middleware) in all my other APIs (Resource servers) that you want to secure using same token. Since you want to consume the token generated by webAPI in your MVC project, after implementing Authorization server you need to do below
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);
Generate a machine key and add it in web.config of your webAPI and ASP.Net MVC site.
With this a cookie will be created and [Authorize] attribute in MVC Site and WebAPI will honor this cookie.
P.S. - I have done this with a web API issuing JWT (Authorization server or Auth & resource server) and successfully able to consume in a ASP.Net MVC website, SPA Site built in Angular , secure APIs built in python (resource server) , spring (resource server), Android App.
You could set the cookie once the user has authenticated against the Account controller.
public class AccountController
{
public HttpResponseMessage Login()
{
// Your authentication logic
var responseMessage = new HttpResponseMessage();
var cookie = new CookieHeaderValue("session-id", "12345");
cookie.Expires = DateTimeOffset.Now.AddDays(1);
cookie.Domain = Request.RequestUri.Host;
cookie.Path = "/";
responseMessage.Headers.AddCookies(new CookieHeaderValue[] { cookie });
return responseMessage;
}
}
To authenticate you can put the [Authenticate] attribute on your Home controller.
public class HomeController
{
[Authenticate]
public ActionResult Index()
{
return View();
}
}
The Authenticate attribute can also be applied at the Controller level if needed.
[Authenticate]
public class HomeController
{
}
You can also make your own authorization attribute if needed by overriding AuthorizeCore and checking for a valid cookie:
public class CustomAuth : AuthenticationAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
HttpCookie authCookie = httpContext.Request.Cookies["CookieName"];
// Your logic
return true;
}
}

How to do programmatic sign in using aspnet Identity Framework v2?

I'm cobbling together snippets of code from blogs and different places to try to get this to work. Normally, I'd refer to the reference documentation, but I can't find it here or anywhere else. It's just videos and demos for specific use cases that include user management or facebook or twitter.
I have a proprietary authentication service that I'm using. User accounts are not managed inside my application. So I need to be able to sign in a user that's completely constructed at run time.
Here's what I'm trying now in my MVC app.
using System.Security.Claims;
public class HomeController : Controller {
public ActionResult Scratch() {
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
var identity = new ClaimsIdentity(claims);
var authenticationManager = HttpContext.GetOwinContext().Authentication;
authenticationManager.SignIn(identity);
return Content(
$"authentication manager type: {authenticationManager.GetType()} \n"
+ $"authenticated: {HttpContext.User.Identity.IsAuthenticated} \n"
+ $"user name: {HttpContext.User.Identity.Name} \n",
"text/plain");
}
}
The output is
authentication manager type: Microsoft.Owin.Security.AuthenticationManager
authenticated: False
user name:
Questions:
Why does the output show that the user has not been authenticated? What more do I have to do to get this user authenticated?
Where is the documentation for this framework?
Update
Startup.cs
public partial class Startup {
public void Configuration(IAppBuilder app) {
ConfigureAuth(app);
ConfigureAnalyticContext(app);
}
}
Startup.Auth.cs:
(there is actually much more, but all the rest has been commented out, in search of finding a minimal configuration that works)
public partial class Startup {
public void ConfigureAuth(IAppBuilder app) {
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
}
AnalyticContext.Auth.cs
(this is my Entity Framework context, I doubt it's related to this problem)
public partial class Startup {
public void ConfigureAnalyticContext(IAppBuilder app) {
app.CreatePerOwinContext(() => CentoAnalyticsContext.Create());
}
}
Well, it seems that you are not using ASP.NET Identity. ASP.NET Identity is new membership system of asp.net, which automatically creates database tables for storing users, encrypting password, etc.
What you are trying to do is to use the new authentication system provided by OWIN, which replaces the old FormsAuthentication style.
To make it work, you have to create the cookie authentication. Like this:
public static class AuthConfig
{
public const string DefaultAuthType = "DefaultAppCookie";
public const string LoginPath = "/System/SignIn";
public static void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthType,
LoginPath = new PathString(LoginPath)
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; //or whatever
}
}
In the login action:
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthConfig.DefaultAuthType);
IAuthenticationManager authManager = Request.GetOwinContext().Authentication;
authManager.SignIn(new AuthenticationProperties() { IsPersistent = true }, identity);
I think that should be enough to make it work in your app. A few days ago I answered a similar question MVC Authentication - Easiest Way, take a look, it might be helpful.
I recently have added Active Directory authentication, constructed ClaimsPrincipal myself and signed-in the same way you do.
And you are indeed missing .UseCookieAuthentication in your ConfigureAuth(IAppBuilder app)
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "MyAuthenticationName", // <-- this must match the AuthenticatioType name when you do sign-out
LoginPath = new PathString("/MyLoginPath"),
CookieName = "MyCookieName",
CookieHttpOnly = true,
});
}
And you don't need UseExternalSignInCookie.
Request.IsAuthenticated will be false with in the same request flow.
I think you still need to update the current security principal if you need to check IsAuthenticated for the request as authenticationManager.SignIn only validates the user against data store and sets the OWIN cookie which when sent back in subsequent request sets the security principal , usually a redirect takes care of this as in most cases there will be redirection in home page or something. If you still need to check with in the same request you can do something like below depending on your requirement
var claims = new Claim[] {
new Claim(ClaimTypes.Name, "somename"),
new Claim(ClaimTypes.NameIdentifier, "someidentifier"),
new Claim("foo", "bar"),
};
var identity = new ClaimsIdentity(claims,DefaultAuthenticationTypes.ApplicationCookie,
ClaimTypes.Name, ClaimTypes.Role);
var principal = new ClaimsPrincipal(identity);
System.Threading.Thread.CurrentPrincipal = principal;
if (System.Web.HttpContext.Current != null)
System.Web.HttpContext.Current.User = principal;
Hope this helps.

Resources