How might I add a secure token to access the api so that not everyone can get it. I would like the format of my url to be : api.example.com/*key*/person?id=5 and when I send this request it will return if the key is valid if not valid it will return invalid login. I am using mvc 4 api and C# to make this and a link or something will be great.
The key phrase you are looking for most like is that you need to create and add a custom ActionFilterAttribute.
A quick search on google turned up this blog article which talks about doing this exact thing (along with some other filters).
Just in case there's some link rot here's the gist (excerpts from the blog article):
Come up with some scheme for generating/verifying the API tokens
Create you attribute that uses the verification from step 1 in an attribute
Add the attribute to the global configuration
CODE
public class TokenValidationAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
string token;
try
{
token = actionContext.Request.Headers.GetValues("Authorization-Token").First();
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest)
{
Content = new StringContent("Missing Authorization-Token")
};
return;
}
try
{
//This part is where you verify the incoming token
AuthorizedUserRepository.GetUsers().First(x => x.Name == RSAClass.Decrypt(token));
base.OnActionExecuting(actionContext);
}
catch (Exception)
{
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
Content = new StringContent("Unauthorized User")
};
return;
}
}
}
}
To make these action filters global, the following code in the Global.asax Application_Start() will do the trick:
var config = GlobalConfiguration.Configuration;
config.Filters.Add(new TokenValidationAttribute());
At my work, we create a hash of the username and password and use that for the user token. You could just generate a GUID for them, keeping track of the time it was created and who it belongs to.
Related
i'm working with asp mvc 5, and i have to schedule some tasks, so i want to create a simple methods to be called from a simple console program C# and schedule them with Windows Task Scheduler.
The think is, i'm using Identity with Authorize attribute to manage the user permissions.
For example, i have the next method:
[Authorize(Roles="Admin")]
public async Task<JsonResult> CriticalTask(string someParam)
{
//procesing data
return null;
}
The think, is:
I dont know how can i do the login to pass the validation from the Authorize(Roles="Admin")
I try creating a simple method to login before it, but that doesn't work
I'm trying some like this
const string URL = "http://localhost:53665/";
RestClient mClient = new RestClient(URL);
const string parameterUserName = "userName";
const string parameterPassword = "password";
const string ruta = "Usuarios/ApiLogin";
var request = new RestRequest(ruta);
request.AddParameter(parameterUserName, "userName");
request.AddParameter(parameterPassword, "password");
//Method to login
var result2 = mClient.Execute(request);
Console.WriteLine($"Login\n{result2.Content}");
//Method that needs Admin permissions
request = new RestRequest("Usuarios/Test");
var result3 = mClient.Execute(request);
Console.WriteLine($"Test\n{result3.Content}");
is that possible only with Authorize attribute? or i need to implement some token method to authorize this calls?
Thanks!
What you should do is to save the received token after login and then add the token to request header which needs to be authorized:
var result = mClient.Execute(request);
string resultContent = result.Content.ReadAsStringAsync().Result;
//This token will be used for authorization
var token = JsonConvert.DeserializeObject<TokenModel>(resultContent);
var request = new RestRequest("Usuarios/Test"); //add token to header of request
mClient.AddDefaultHeader("Authorization", string.Format("bearer {0}", token.Access_Token));
var result3 = mClient.Execute(request);
Token model:
public class TokenModel
{
...
public string Access_Token { get; set; }
...
}
The easiest solution would be to use BasicAuth - you pass Credentials in headers for each request, and each request is validated separately - search for MVC Basic auth fr sample setup. I's the easiest form - but also very insecure as you pass your credentials in each call in almost plain text (it is only base64 of your credentials)
I'd suggest you to use Identity Server 4 to authorize your client using bearer token.
this way before first call you request token from server and then pass it to following requests and use this to authorize your api calls.
see following tutorial about setup.
http://docs.identityserver.io/en/aspnetcore1/quickstarts/6_aspnet_identity.html
in following url you can see example of in memory users, but also token requests.it's easy but obsolete
https://neelbhatt.com/2018/03/04/step-by-step-setup-for-the-auth-server-and-the-client-identityserver4-with-net-core-part-ii/
you can also use some sort of following code to obtain token in less obsolete way:
using (var httpClient = new HttpClient()) {
var discovery = await _httpClient.GetDiscoveryDocumentAsync(_configuration["ApiBaseAddress"]);
if (discovery.IsError)
{
return false;
}
request.Address = discovery.TokenEndpoint;
request.ClientId = _configuration["AuthClientName"];
request.ClientSecret = _configuration["AuthClientSecret"];
var request = new PasswordTokenRequest
{
UserName = "yourUserName",
Password = "yourPassword"
};
var token = await _httpClient.RequestPasswordTokenAsync(request);
}
in token.AccessToken you have your access token - the one needed to be sent to call api. you also have your refresh token in token.RefreshToken - it will be useful later
then to send call simply add bearer token to your HttpRequestMessage and it's done
var _httpClient = new HttpClient();
//Configure your http client here
var req= new HttpRequestMessage("http request method", "request uri");
req.SetBearerToken("your access token goes here);
var result = await _httpClient.SendAsync(req);
keep in mind that after you receive permission denied it's better to refresh token than obtain another one (you don't need to send your credentials). You use your refresh token i mentioned earlier.
The code for refreshing token is very similar to obtaining token by login/password. Just instead of PasswordTokenRequest class use following class:
var request = new RefreshTokenRequest
{
RefreshToken = _refreshToken
};
and instead httpClient.RequestPasswordTokenAsync(request) use httpClient.RequestRefreshTokenAsync(request). rest of code may remain similar.
I end up creating a custom attribute, based on the reply posted by crrlos in StackOverflow in Spanish.
I will translate it as best I can so that it can serve others
What you can do is create a custom authorization attribute, for that
you create a class that inherits AuthorizeAttribute and override the
AuthorizeCore method. The modification consists in passing to the
route an additional parameter to indicate that it is calling from the
task scheduler, if the parameter is not found then it will perform the
normal validation, if the parameter is found (it must have a value or
if it will not be null ) Then take the credentials of the url and
perform the validation, if they are correct return a true allowing
access to the method.
public class CustomAuthorization : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
//get special parameter indicating that the request was made from the task scheduler
var parametro = httpContext.Request.QueryString.Get("parametro_especial");
if(parametro != null)
{
// get access credentials and validate them
// if they are valid, return true
}
//if they are not valid, or aren't present
//try with deffault validate.
return base.AuthorizeCore(httpContext);
}
}
How to use it?
[CustomAuthorization (Roles = "Admin")]
public JsonResult CargarTodosLosArticulos()
{
return null;
}
I have to do SSO authentication with saml2 for my existing asp.net web application.
I am using Sustainsys.Saml2.Owin example to do that.
Identity provider is Azure ADFS ( https://sts.windows.net/TENANTID )
I have configured the Startup file. It loads the metadata file and certificate.
And in my Login page, I am challenging if not authenticated.
It is successfully redirecting to the login page but the Request is never getting authenticated after the login. And in the reply URL we are getting error=access_denied
[neither Request.IsAuthenticated or owinContext.Authentication.User.Identity.IsAuthenticated is set to true]
So it keep on challenging for many times and error with bad request.
What I am doing wrong?
Which module of Owin/Sustainsys is reposnsible to set the IsAuthenticated status?
*a Saml2. cookie [Saml2.DAeP63c***UTX0h***_***] is passed along with the request after login into Microsoft [https://login.microsoftonline.com/TENANTID/saml2]
Startup.cs file
public void ConfigureAuth(IAppBuilder appBuilder)
{
try
{
appBuilder.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions());
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseSaml2Authentication(CreateSaml2Options());
}
catch (Exception exp)
{
}
}
private Saml2AuthenticationOptions CreateSaml2Options()
{
try
{
var spOptions = CreateSPOptions();
var Saml2AuthOptions = new Saml2AuthenticationOptions(false)
{
SPOptions = spOptions,
Notifications = new Saml2Notifications(),
};
var idp = new IdentityProvider(new EntityId(authority), spOptions)
{
MetadataLocation = metadataLocation,
Binding = Saml2BindingType.HttpRedirect
};
idp.SigningKeys.AddConfiguredKey(
new X509Certificate2(certificateLocation));
Saml2AuthOptions.IdentityProviders.Add(idp);
return Saml2AuthOptions;
}
catch (Exception exp)
{
}
}
private SPOptions CreateSPOptions()
{
try
{
var engAus = "en-AU";
var organization = new Organization();
var spOptions = new SPOptions
{
EntityId = new EntityId(ApplicationId),
ReturnUrl = new Uri(redirectUrl),
Organization = organization,
};
return spOptions;
}
catch (Exception exp)
{
}
}
Login.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
IOwinContext owinContext = HttpContext.Current.GetOwinContext();
//if (Request.IsAuthenticated)
if (owinContext.Authentication.User != null &&
owinContext.Authentication.User.Identity != null &&
owinContext.Authentication.User.Identity.IsAuthenticated)
{
//Authenticated
string name = owinContext.Authentication.User.Identity.Name;
}
else
{
var authenticationTypes = owinContext.Authentication.GetAuthenticationTypes().Select(d => d.AuthenticationType).ToArray();
owinContext.Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, authenticationTypes);
}
}
}
(all code posted here are the same sample from Github)
You need to understand how SAML works, here's a simple saml implementation class that I used before I dive into SustainsysSAML. AspNetSaml
This is the basic flow of SAML Implementation:
User access your app, if user is not yet authenticated your app should redirect the user to your saml provider.
//specify the SAML provider url here, aka "Endpoint"
var samlEndpoint = "http://saml-provider-that-we-use.com/login/";
var request = new AuthRequest(
"http://www.myapp.com", //put your app's "unique ID" here
"http://www.myapp.com/SamlConsume" //assertion Consumer Url - the redirect URL where the provider will send authenticated users
);
//generate the provider URL
string url = request.GetRedirectUrl(samlEndpoint);
//then redirect your user to the above "url" var
//for example, like this:
Response.Redirect(url);
From saml provider, user enters credentials and if valid user, saml provider will authenticate and redirect the user to your app.
SAML provider will post the samlresponse to your app (eg. http://www.myapp.com/SamlConsum).
//ASP.NET MVC action method... But you can easily modify the code for Web-forms etc.
public ActionResult SamlConsume()
{
//specify the certificate that your SAML provider has given to you
string samlCertificate = #"-----BEGIN CERTIFICATE-----
BLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAHBLAH123543==
-----END CERTIFICATE-----";
Saml.Response samlResponse = new Response(samlCertificate);
samlResponse.LoadXmlFromBase64(Request.Form["SAMLResponse"]); //SAML providers usually POST the data into this var
if (samlResponse.IsValid())
{
//WOOHOO!!! user is logged in
//YAY!
//Some more optional stuff for you
//lets extract username/firstname etc
string username, email, firstname, lastname;
try
{
username = samlResponse.GetNameID();
email = samlResponse.GetEmail();
firstname = samlResponse.GetFirstName();
lastname = samlResponse.GetLastName();
}
catch(Exception ex)
{
//insert error handling code
//no, really, please do
return null;
}
//user has been authenticated, put your code here, like set a cookie or something...
//or call FormsAuthentication.SetAuthCookie() or something
}
}
Your app will read the samlresponse and if valid will let the user use your app, your app will now handle the roles of the user depending on your policies.
Some tips:
Make sure your app is identifiable by your saml provider.
Use Firebug to trace your http requests (or any http tracing tool)
Understand the difference between samlresponse and samlrequest
Using Firebug you should be able to see the samlresponse.
If you have multiple web apps that you want to have SSO using your saml provider. I suggest you create an httprequest/httphandler to handle the samlresponse from your provider. You can then install this dll to your server and just add the handler to each web app's config. No code change require to your web apps :).
I hope this helps.
I'm looking for a minimal example for a custom authentication writen in C# for asp.net core 2 based on for example API keys.
Mircosoft has a pretty good documentation about doing this with cookies, however this is not what I want. Since I want to use API keys (given by http-header, GET or Cookie, ...) I never make a call to HttpContext.SignInAsync and this is maybe the issue I can't find/google my way around.
I built an simple AuthenticationHandler (based on this - since I read that custom middlewares are not the way to go anymore) which looks something like this:
internal class CustomAuthHandler : AuthenticationHandler<CustomAuthOptions>
{
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// parse cookies to find APIKEY
if(Context.Request.Cookies.ContainsKey("APIKEY"))
{
string APIKEY = Request.Cookies["APIKEY"];
// ... checking DB for APIKEY ...
// creating claims
var claims = new[]
{
new Claim( /* ... */ ),
// ...
};
var claimsIdentity = new ClaimsIdentity(claims);
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
var ticket = new AuthenticationTicket(claimsPrincipal, new AuthenticationProperties(), "Custom Scheme");
return AuthenticateResult.Success(ticket); // this line gets called
}
return AuthenticateResult.NoResult();
}
}
But when I have an API endpoint with just the [Authorize] attribute the DenyAnonymousAuthorizationRequirement denies the request cause the user is not allowed (cause IsAuthenticated == false which is readonly, all claims are shown properly)
Change var claimsIdentity = new ClaimsIdentity(claims); into something like var claimsIdentity = new ClaimsIdentity(claims, "Password"); (of course, instead of "Password" use the AuthenticationType that best fits your case).
Similar question here: Why is my ClaimsIdentity IsAuthenticated always false (for web api Authorize filter)?
My setup,
An IdentityServer using MVC Identity to store the Users, created with dotnet new mvc -au Individual and applying the http://docs.identityserver.io/en/release/quickstarts/0_overview.html tutorial, running in localhost 5000.
A client App, but now I'm using postman to do tests.
A WEB API, created with dotnet new webapi, running in localhost 5001.
The IdentityServer resources and clients configuration is the following, notice that I'm using reference tokens:
public static IEnumerable<IdentityResource> GetIdentityResources() {
return new List<IdentityResource>{ new IdentityResources.OpenId() };
}
public static IEnumerable<ApiResource> GetApiResources() {
return new List<ApiResource>{
new ApiResource("api_resource", "API Resource") {
Description= "API Resource Access",
ApiSecrets= new List<Secret> { new Secret("apiSecret".Sha256()) },
}
};
}
public static IEnumerable<Client> GetClients() {
return new List<Client>{
new Client {
ClientId= "angular-client",
ClientSecrets= { new Secret("secret".Sha256()) },
AllowedGrantTypes= GrantTypes.ResourceOwnerPassword,
AllowOfflineAccess= true,
AccessTokenType = AccessTokenType.Reference,
AlwaysIncludeUserClaimsInIdToken= true,
AllowedScopes= { "api_resource" }
}
}
The password and user is send with postman and the token received is send to the WEB API also with postman, something like call localhost:5001/v1/test with the token pasted in option bearer token.
In the API Startup, in ConfigureServices I'm adding the lines below
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority= "http://localhost:5000";
options.ApiName= "api_resource";
options.ApiSecret = "apiSecret";
});
And I'm getting the Id of the user inside the controller as follows:
public async Task<IActionResult> Get(int id) {
var discoveryClient = new DiscoveryClient("http://localhost:5000");
var doc = await discoveryClient.GetAsync();
var introspectionClient = new IntrospectionClient(
doc.IntrospectionEndpoint,
"api_resource",
"apiSecret");
var token= await HttpContext.GetTokenAsync("access_token");
var response = await introspectionClient.SendAsync(
new IntrospectionRequest { Token = token });
var userId = response.Claims.Single(c => c.Type == "sub").Value;
}
The question itself is, am I using the right path to get the Id from the reference token?, because now It works but I don't want to miss anything, specially thinking that is a security concern.
I'm asking also because I have seen anothers using
string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;
that is more straightforward but doesn't seems to fit with reference tokens.
Thanks in advance.
Inside a controller action that is protected with an [Authorize] attribute you can simply get claims directly from the ClaimsPrinciple, without having to go through a manual discovery client. The claims principle is handily aliased simply with User inside your controllers.
I'm asking also because I have seen anothers using
string userId = User.Claims.FirstOrDefault(c => c.Type ==
ClaimTypes.NameIdentifier).Value;
that is more straightforward but doesn't seems to fit with reference
tokens.
It works just fine with reference tokens. You should have no problems accessing the sub claim.
EDIT:
As I mentioned in a comment below, I tend to use the standard JwtClaimTypes and create some extension methods on the ClaimsPrinciple, such as:
public static string GetSub(this ClaimsPrincipal principal)
{
return principal?.FindFirst(x => x.Type.Equals(JwtClaimTypes.Subject))?.Value;
}
or
public static string GetEmail(this ClaimsPrincipal principal)
{
return principal?.FindFirst(x => x.Type.Equals(JwtClaimTypes.Email))?.Value;
}
... so that within my protected actions I can simply use User.GetEmail() to get hold of claim values.
It's worth stating the obvious, that any method for retrieving claim values will only work if the claims actually exist. i.e. asking for the ZoneInfo claim will not work unless that claim was requested as part of the token request in the first place.
Here is how I've created an claim based authorization attribute. But I have some doubts about how this work.
Given the code from my startup class:
public void Configuration(IAppBuilder app)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = ConfigurationManager.AppSettings["Authentication:Authority"],
RequiredScopes = ConfigurationManager.AppSettings["Authentication:Scopes"].Split(' ').ToList(),
PreserveAccessToken = true
});
}
I was expecting that if I have this attribute to my controller and I send an invalid token(invalid signature) the request will be automatically rejected as unauthorized, but the code from the attribute is executed.
Shouldn't OWIN validate the token first?
How to make sure that the token is valid (valid stricture, signature, not expired, etc) and only after validate the claims?
The issue is within your linked question in your ClaimAuthorizationAttribute - it doesn't ever call base.IsAuthorized(), thus bypassing the built in protection mechanisms offered by AuthorizeAttribute.
Instead of just returning here after seeing whether or not the claim is present:
return token.Claims.Any(c => c.Type.Equals(this.Claim) && c.Value.Equals("True", StringComparison.OrdinalIgnoreCase));
You should instead carry on with making sure that the base class is satisfied, and thus the token itself is valid, too:
var claimValid = token.Claims.Any(c => c.Type.Equals(this.Claim) && c.Value.Equals("True", StringComparison.OrdinalIgnoreCase));
if (claimValid)
return base.IsAuthorized();
else
return false;