I am using OWIN middleware to configure OpenID Authentication. This configuration is called at StartUp.cs points to a B2C IDP.
public void ConfigureAuth(IAppBuilder app)
{
// Required for Azure webapps, as by default they force TLS 1.2 and this project attempts 1.0
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebChunkingCookieManager()
});
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = Globals.ClientId,
RedirectUri = Globals.RedirectUri,
PostLogoutRedirectUri = Globals.RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claim type that specifies the Name property.
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
ValidateIssuer = false
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {Globals.ReadTasksScope} {Globals.WriteTasksScope}",
// ASP.NET web host compatible cookie manager
CookieManager = new SystemWebCookieManager()
}
);
}
How can I get the middleware to use different configurations, specifically for the object OpenIdConnectAuthenticationOptions, in order to point to a different IDP at runtime?
You can register multiple named openIDCConnect handler like
.AddOpenIdConnect("Auth0", options =>
{ Options...
}
.AddOpenIdConnect("google", options =>
{ Options...
}
.AddOpenIdConnect("facebook", options =>
{ Options...
}
Then the user can choose how he wants to authenticate, using one of :
HttpContext.SignInAsync("Auth0",....);
HttpContext.SignInAsync("google",....);
HttpContext.SignInAsync("facebook",....);
When you add multiple handlers, you need to make sure the local callback path in the client is different for each handler, like
CallbackPath = new PathString("/signin-auth0");
CallbackPath = new PathString("/signin-google");
CallbackPath = new PathString("/signin-facebook");
(You set this in the options)
Related
Using Identity Server 4, how do you hook into the exchange between the client and server when using the authorization_code flow to provide Client credentials to the Identity Server using a JWT Token?
Below is the solution:
In ConfigureServices the is key to hook into the Identity Server pipeline and provide a call-back for the event OnAuthorizationCodeReceived. This event is called at the point in the pipeline where the authorization code is received back from Identity server during the normal exchange between the client and server as described by https://www.ietf.org/rfc/rfc6750.txt.
Doing this give you the opportunity to create the JWT token and make it available from that point on in the pipeline.
Configuration on the client
services.AddAuthentication(options =>
...
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.RemoteAuthenticationTimeout = TimeSpan.FromMinutes(10);
options.UseTokenLifetime = false;
options.RequireHttpsMetadata = false;
options.Authority = "http://localhost:44320/";
options.ClientId = "cliend-id";
options.ClientSecret = "client-secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Events.OnAuthorizationCodeReceived = delegate (AuthorizationCodeReceivedContext context)
{
var clientassertion = new ClientAssertion("client-id", "http://localhost:44320/connect/token");
var assertion = clientassertion.CreateJwtClientAssertionAsymmetric("localhost");
context.TokenEndpointRequest.ClientAssertion = assertion.ClientAssertion;
context.TokenEndpointRequest.ClientAssertionType = assertion.ClientAssertionType;
return Task.CompletedTask;
};
...
Configuration on the server
As http://docs.identityserver.io/en/release/topics/secrets.html?highlight=beyond indicates under the section beyond shared secrets.
The important bit here is to ensure the type and value are aligned as in the example below.
var client = new Client
{
...
ClientSecrets =
{
new Secret
{
Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
Value = "MIIDATCC..."
}
},
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
AllowedScopes = { "api1", "api2" }
};
Implementation
Implement the interfaces ISecretParser and ISecretValidator
Then add to implementations to the DI system in ConfigureServices.
Eg.
builder.AddSecretParser()
builder.AddSecretValidator()
If your Validator is not getting called, ensure RequireClientSecret is set to true.
Take the appropriate action in the parser, and validator (from the parse return success for failure).
This approach work for both private_key_jwt and client_secret_jwt.
I have created an identity server 4 project and a mvc client project. Authentication flow works as expected. I have added an API controller in the same project as identity server and i want to hit this api resource from mvc client.Essentially,i need both identity server middleware and token validation middle inside the idenity server project.
If you haven't already, add these Nuget packages to your already established IdentityServer app/site:
IdentityServer4.AccessTokenValidation
Microsoft.AspNetCore.Mvc
Add another Api Resource to your resources list:
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API"),
new ApiResource("api2", "IdentityServer API")
};
}
Update your client configuration to allow api2:
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "mvc",
... omitted
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api2"
}
}
};
}
In the IdentityServer's Configure method in Startup add:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ApiName = "api2"
});
I have a WebAPI with 2 areas - user and admin. 2 sites, user and admin, use it, they have their own client ids.
public static readonly Scope AdminScope = new Scope
{
Name = "adm_api",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim>
{
new ScopeClaim(Constants.ClaimTypes.Role),
new ScopeClaim(VitClaimTypes.IsAdmin)
},
};
public static readonly Scope UserScope = new Scope
{
Name = "user_api",
Type = ScopeType.Resource,
Claims = new List<ScopeClaim>
{
new ScopeClaim(Constants.ClaimTypes.Role),
new ScopeClaim(Constants.ClaimTypes.Name),
}
};
Clients:
new Client
{
ClientName = "User area client",
ClientId = "user_client",
Enabled = true,
AllowedScopes = new List<string>
{
"user_api", "offline_access"
}
},
new Client
{
ClientName = "Admin area client",
ClientId = "adm_client",
Enabled = true,
AllowedScopes = new List<string>
{
"user_api", "adm_api"
}
},
Now I want to deny login for users who request 'adm_api' scope but do not have IsAdmin claim. How do I do that? I know that I can add a custom Authorize attribute to the API, and I will do that. But it would be more user-friendly to deny access right away on login than to wait for the first API access.
I think that would rather be your admin applications responsibility to check for the issued claims and inform the user of insufficient access before requesting api resources. It wouldn't be correct for identity service to deny login, after all, there is nothing wrong with authentication. This sounds more like authorization.
I am having an identical problem to this but their solution doesn't work for me.
I'm calling a WebAPI method (.Net 4.5.2) and the project has a reference to IdentityModel 1.13.1 and it is protected using IdentityServer 3 with the following code in the startup class -
JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44305/core/",
RequiredScopes = new[] { "read", "write" },
// client credentials for the introspection endpoint
ClientId = "clientcredentials.client",
ClientSecret = "secret"
});
The clients configuration in the IdentityServer startup includes the following client definition -
new Client
{
ClientName = "Mobile Api Client",
Enabled = true,
ClientId = "clientcredentials.client",
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256()),
new Secret
{
Value = "[valid thumbprint]",
Type = "X509Thumbprint",
Description = "Client Certificate"
},
},
AllowedScopes = new List<string>
{
"read",
"write"
},
Claims = new List<Claim>
{
new Claim("location", "datacenter")
}
}
And in the Xamarin client (which also uses IdentityModel 1.13.1) ...
var token = IdentityServerClient.RequestClientToken(); // this returns a valid bearer token
TokenResultLabel.Text = token.Raw;
HttpClient apiClient = new HttpClient();
apiClient.SetBearerToken(token.AccessToken);
var result = await apiClient.GetStringAsync("[valid api URL]");
ApiResultLabel.Text = result;
I've tried it with IdentityModel 2.0 (latest compatible version), 1.13.1 (the version mentioned in the referenced question, and 1.9.2 (the version in the IdentityServer 3 samples)
Any help would be greatly appreciated
While the configuration allows the client to request both the read and write scopes, do you explicitly specify when you request the access token that you wish to get one containing these 2 scopes? This should happen in your IdentityServerClient.RequestClientToken method.
Allowing a client to request scopes doesn't mean these scopes will automatically be included in access tokens returned by IdentityServer.
I have a web app MVC,using auth0 owin regular web app cookie based authentication.
This web app also has webapis which is used internally in the application. However i have a requirement to call this webapis from outside the application. So i created a restclient and tried to implement jwtbearerauthentication in application (but cookie based on authentication still in place).
Now when i call the webapi from other application it validates the bearer token gives no error however it redirects to login page due to cookie based authentication.
startup file:
public partial class Startup
{
private IPlatform platform;
public void ConfigureAuth(IAppBuilder app, IPlatform p, IContainer container)
{
platform = p;
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
ExpireTimeSpan = System.TimeSpan.FromDays(2),
SlidingExpiration = true
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
var provider = new Auth0.Owin.Auth0AuthenticationProvider
{
OnReturnEndpoint = (context) =>
{
// xsrf validation
if (context.Request.Query["state"] != null && context.Request.Query["state"].Contains("xsrf="))
{
var state = HttpUtility.ParseQueryString(context.Request.Query["state"]);
AntiForgery.Validate(context.Request.Cookies["__RequestVerificationToken"], state["xsrf"]);
}
return System.Threading.Tasks.Task.FromResult(0);
},
OnAuthenticated = (context) =>
{
var identity = context.Identity;
//Add claims
var authenticationManager = container.Resolve<IAuthenticationManager>();
authenticationManager.AddClaims(identity);
if (context.Request.Query["state"] != null)
{
authenticationManager.AddReturnUrlInClaims(identity, context.Request.Query["state"]);
}
return System.Threading.Tasks.Task.FromResult(0);
}
};
var issuer = "https://" + ConfigurationManager.AppSettings["auth0:Domain"] + "/";
var audience = ConfigurationManager.AppSettings["auth0:ClientId"];
var secret = TextEncodings.Base64.Encode(TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["auth0:ClientSecret"]));
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
app.UseAuth0Authentication(
clientId: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientId"),
clientSecret: platform.ServerRole.GetConfigurationSettingValue("auth0:ClientSecret"),
domain: platform.ServerRole.GetConfigurationSettingValue("auth0:Domain"),
provider: provider);
}
}
webapiconfig file:
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional});
config.Filters.Add(new AuthorizeAttribute());
ODataConfig.Setup(config);
var clientID = WebConfigurationManager.AppSettings["auth0:ClientId"];
var clientSecret = WebConfigurationManager.AppSettings["auth0:ClientSecret"];
config.MessageHandlers.Add(new JsonWebTokenValidationHandler()
{
Audience = clientID,
SymmetricKey = clientSecret
});
}
Currently creating the jwt token from below code and posting using postman in header just to check if it works.. but redirects to login page.
string token = JWT.Encode(payload, secretKey, JwsAlgorithm.HS256);
I suspect what's happening is that your call to the API has a bearer token which fails validation (or there is no Authorize token at all), your API controller has an Authorize attribute, which, since there is no valid ClaimsPrincipal on the call throws 401. Auth0AuthenticationProvider picks that and assumes the call was to UI so redirects for user authentication. You may want to add an override in the Oauth0Provider to trap OnRedirectToIdP (or something like that), inspect the request and if it is to API, abot further handling and return Unauthorized.
Remove any [Authorize] from your API and see whether it works then. Also make sure your startup does not require Authorize for all controllers.
You may want to remove the authn part of your code (cookie and Oauth2Provider) and see whether you are getting to the API then.
A few years late i know, but i recently came across the same requirement in a project, and found this sample put together by a dev at Auth0.
https://github.com/auth0-samples/aspnet-core-mvc-plus-webapi
The example in the link allows for cookie authentication OR token authentication for the API endpoints.
The key takeaway for me was using attributes on your routes to tell the pipline what authentication mechanism to use. In my case i wanted cookie authentication for the UI and token authentication for the endpoints. i had no requirement to use both for any single area of the project.
controller:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HttpGet]
[Route("api")]
public string TestAuth()
{
return "All good " + this.User.FindFirst(ClaimTypes.NameIdentifier).Value + ". You only get this message if you are authenticated.";
}