I want to change the value of a claim via refresh tokens. My refresh token provider is like this:
public class MyRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
...
var claim = context.Ticket.Identity.FindFirst(ClaimTypes.UserData);
if (claim != null)
{
context.Ticket.Identity.RemoveClaim(claim);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserData, "New Value"));
}
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
And in the startup class:
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
...
RefreshTokenProvider = new MyRefreshTokenProvider()
});
The refresh token request completes with no error. But when I use the new access token, the claim value is still the old one.
Is my approach right? Or how can I change a claim value in the Bearer authentication?
Finally I've found the solution. I have to extend the AccessTokenProvider OAuthAuthorizationServerOptions instead of the RefreshTokenProvider:
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
...
AccessTokenProvider = new MyAccessTokenProvider(),
RefreshTokenProvider = new MyRefreshTokenProvider()
});
public class MyAccessTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
...
var claim = context.Ticket.Identity.FindFirst(ClaimTypes.UserData);
if (claim != null)
{
context.Ticket.Identity.RemoveClaim(claim);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserData, "New Value"));
}
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
public class MyRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
According to the OAuthAuthorizationServerHandler class in the Microsoft.Owin.Security.OAuth the AccessTokenProvider only can update the refreshing token. For changing the claims the AccessTokenProvider should be extended:
private async Task InvokeTokenEndpointAsync()
{
...
var accessTokenContext = new AuthenticationTokenCreateContext(
Context,
Options.AccessTokenFormat,
ticket);
await Options.AccessTokenProvider.CreateAsync(accessTokenContext);
string accessToken = accessTokenContext.Token;
if (string.IsNullOrEmpty(accessToken))
{
accessToken = accessTokenContext.SerializeTicket();
}
DateTimeOffset? accessTokenExpiresUtc = ticket.Properties.ExpiresUtc;
var refreshTokenCreateContext = new AuthenticationTokenCreateContext(
Context,
Options.RefreshTokenFormat,
accessTokenContext.Ticket);
await Options.RefreshTokenProvider.CreateAsync(refreshTokenCreateContext);
string refreshToken = refreshTokenCreateContext.Token;
var memory = new MemoryStream();
byte[] body;
using (var writer = new JsonTextWriter(new StreamWriter(memory)))
{
writer.WriteStartObject();
writer.WritePropertyName(Constants.Parameters.AccessToken);
writer.WriteValue(accessToken);
writer.WritePropertyName(Constants.Parameters.TokenType);
writer.WriteValue(Constants.TokenTypes.Bearer);
if (accessTokenExpiresUtc.HasValue)
{
TimeSpan? expiresTimeSpan = accessTokenExpiresUtc - currentUtc;
var expiresIn = (long)expiresTimeSpan.Value.TotalSeconds;
if (expiresIn > 0)
{
writer.WritePropertyName(Constants.Parameters.ExpiresIn);
writer.WriteValue(expiresIn);
}
}
if (!String.IsNullOrEmpty(refreshToken))
{
writer.WritePropertyName(Constants.Parameters.RefreshToken);
writer.WriteValue(refreshToken);
}
...
Related
I have an ASP.Net Web API 2 with BasicAuthenticationAttribute that is working as expected. In my application, there are different controllers and I want to add bearer token-based authentication to one of my controllers. I added those NuGet packages:
Microsoft.AspNet.WebApi.Owin
Microsoft.Owin.Host.SystemWeb
Microsoft.Owin.Security.OAuth
Here is the WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
config.Formatters.JsonFormatter.SerializerSettings.DateTimeZoneHandling =
DateTimeZoneHandling.Local;
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
"DefaultApi",
"api/{controller}/{id}",
new {id = RouteParameter.Optional}
);
config.MessageHandlers.Add(new RequestResponseHandler());
config.Filters.Add(new CustomExceptionFilter());
var resolver = config.DependencyResolver; //Assuming one is set.
var basicAuth = (BasicAuthenticationAttribute)resolver.GetService(typeof(BasicAuthenticationAttribute));
// Web API configuration and services
if (basicAuth != null) config.Filters.Add(basicAuth);
}
}
Here is the Owin Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var configuration = GlobalConfiguration.Configuration;
WebApiConfig.Register(configuration);
app.UseWebApi(configuration);
Configure(app);
}
private static void Configure(IAppBuilder app)
{
var options = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
AllowInsecureHttp = true,
Provider = new AuthorizationServerProvider()
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Here is the controller
[RoutePrefix("api/v2/game/abc101")]
public class A101Controller : ApiController
{
private readonly IGameServicesABC101 _gameServices;
private readonly IMapper _mapper;
public A101Controller(IGameServicesABC101 gameServices, IMapper mapper)
{
_gameServices = gameServices;
_mapper = mapper;
}
[HttpPost]
[Authorize]
[Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
...
Basic Authentication Attribute
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
private const string Realm = "My Realm";
private readonly Func<IUserValidate> _factory;
public BasicAuthenticationAttribute(Func<IUserValidate> factory)
{
_factory = factory;
}
public override void OnAuthorization(HttpActionContext actionContext)
{
if (actionContext.Request.Headers.Authorization == null)
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized)
actionContext.Response.Headers.Add("WWW-Authenticate",
$"Basic realm=\"{Realm}\"");
}
else
{
var authenticationToken = actionContext.Request.Headers
.Authorization.Parameter;
try
{
//Decode the string
var decodedAuthenticationToken = Encoding.UTF8.GetString(
Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var username = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var uv = _factory();
if (uv.Login(username, password))
{
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) HttpContext.Current.User = principal;
}
else
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
catch
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
}
I am using Unity in my application. Basic Authentication works as expected. When I make a request without a token to ...api/v2/game/abc101/purchase I get a response either. Shouldn't I get 401? What I am missing?
UPDATE
I am searching and trying to find how to use both basic authentication and token-based authentication for different controllers. Here is my status update.
There is no code in the Global.asax
Here is the Owin Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
WebApiConfig.Register(config);
app.UseWebApi(config);
Configure(app, config.DependencyResolver);
config.EnsureInitialized();
}
private static void Configure(IAppBuilder app, IDependencyResolver resolver)
{
var options = new OAuthAuthorizationServerOptions()
{
TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
AllowInsecureHttp = true,
Provider = new AuthorizationServerProvider((IUserValidate)resolver.GetService(typeof(IUserValidate)))
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
app.UseOAuthAuthorizationServer(options);
}
}
Here is AuthorizationServerProvider
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private readonly IUserValidate _userValidate;
public AuthorizationServerProvider(IUserValidate userValidate)
{
_userValidate = userValidate;
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (!context.TryGetBasicCredentials(out var clientId, out var clientSecret))
{
context.SetError("Error", "Error...");
}
if (_userValidate.Login(clientId, clientSecret))
{
context.Validated();
}
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Content-Type" });
if (_userValidate.Login(context.UserName, context.Password))
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "admin"));
context.Validated(identity);
}
else
{
context.SetError("Error", "Error...");
}
}
}
The rest is the same as the previous code samples.
When I call ...api/v2/game/abc101/purchase I am getting 401, it is progress. But when I call http://localhost:52908/token I am getting unsupported_grant_type. I am sending requests via Postman and I am sending a POST requests with content-type x-www-form-urlencoded. Grant-Type is password and username/password is also correct.
When I call another controller http://localhost:52908/api/v2/game/purchase basic authentication does NOT work!
Hope someone can help.
UPDATE 1
Now I am getting the token, one step at a time :) How can I also use Basic authentication for another controller?
Here is Startup
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = GlobalConfiguration.Configuration;
Configure(app, config.DependencyResolver);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
config.EnsureInitialized();
}
private static void Configure(IAppBuilder app, IDependencyResolver resolver)
{
var options = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new Microsoft.Owin.PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(1),
Provider = new AuthorizationServerProvider((IUserValidate)resolver.GetService(typeof(IUserValidate)))
};
app.UseOAuthAuthorizationServer(options);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Here is the Authorization Server Provider
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
private readonly IUserValidate _userValidate;
public AuthorizationServerProvider(IUserValidate userValidate)
{
_userValidate = userValidate;
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
if (!context.TryGetBasicCredentials(out var clientId, out var clientSecret))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
}
if (_userValidate.Login(clientId, clientSecret))
{
context.Validated();
}
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (_userValidate.Login(context.UserName, context.Password))
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "admin"));
context.Validated(identity);
}
else
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
}
}
}
As I mentioned before, I have Basic Authentication Attribute and somehow I have to use it in my other controller.
UPDATE 2
How can I use OverrideAuthentication and my basic authentication attribute?
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
private const string Realm = "My Realm";
private readonly Func<IUserValidate> _factory;
public BasicAuthenticationAttribute(Func<IUserValidate> factory)
{
_factory = factory;
}
...
UPDATE 3
I tried this in my basic authentication attribute OnAuthorization method;
var authentication = DependencyResolver.Current.GetService<IUserValidate>();
if (authentication.Login(username, password))
{
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) HttpContext.Current.User = principal;
}
There are 2 problems, authentication is null, and somehow authentication token in the attribute is the bearer authentication username/password even though I use basic authentication username/password in the request. It's very weird!
/Get the authentication token from the request header
var authenticationToken = actionContext.Request.Headers
.Authorization.Parameter;
Any help please?
Thanks in advance.
After long googling, here is how I managed to use both basic authentication and bearer authentication for my different controllers.
In Custom basic authentication Attribute I used dependency and requestScope.GetService.
public class CustomBasicAuthenticationAttribute : AuthorizationFilterAttribute
{
[Dependency] public static IUserValidate authentication { get; set; }
private const string Realm = "My Realm";
public override void OnAuthorization(HttpActionContext actionContext)
{
var requestScope = actionContext.Request.GetDependencyScope();
//If the Authorization header is empty or null
//then return Unauthorized
if (actionContext.Request.Headers.Authorization == null)
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
// If the request was unauthorized, add the WWW-Authenticate header
// to the response which indicates that it require basic authentication
if (actionContext.Response.StatusCode == HttpStatusCode.Unauthorized)
actionContext.Response.Headers.Add("WWW-Authenticate",
$"Basic realm=\"{Realm}\"");
}
else
{
//Get the authentication token from the request header
var authenticationToken = actionContext.Request.Headers
.Authorization.Parameter;
try
{
//Decode the string
var decodedAuthenticationToken = Encoding.UTF8.GetString(
Convert.FromBase64String(authenticationToken));
//Convert the string into an string array
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
//First element of the array is the username
var username = usernamePasswordArray[0];
//Second element of the array is the password
var password = usernamePasswordArray[1];
authentication = requestScope.GetService(typeof(IUserValidate)) as IUserValidate;
if (authentication != null && authentication.Login(username, password))
{
var identity = new GenericIdentity(username);
IPrincipal principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null) HttpContext.Current.User = principal;
}
else
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
catch
{
actionContext.Response = actionContext.Request
.CreateResponse(HttpStatusCode.Unauthorized);
}
}
}
}
In one of my controller, I added those attributes
[OverrideAuthentication]
[CustomBasicAuthentication]
[HttpPost, Route("purchase")]
public async Task<IHttpActionResult> PurchaseGame(RequestDto game)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
...
Now I can use bearer token authentication for ...api/v2/game/abc101/purchase and basic authentication for ...api/v2/game/purchase.
Update
The vital part is the dependency and actionContext.Request.GetDependencyScope();. Without OverrideAuthentication it is working as expected.
Hope this solution helps for others.
Can not get an error message in HTTP response.
if (!string.IsNullOrWhiteSpace(checkTokenResponse.Error))
{
Logger.LogError(checkTokenResponse.Error);
return AuthenticateResult.Fail(checkTokenResponse.Error);
}
response
I suppose the error message should render into data response field
The same question ASP.NET Core - getting a message back from AuthenticationHandler
Update
code of custom Auth handler
public class AuthHandler : AuthenticationHandler<AuthOptions>
{
private readonly IPrimeApiProxy _primeApiProxy;
private readonly IPrimeProxy _primeProxy;
private readonly int _adminGroupId;
private static readonly string HeaderName = "Authorization";
private static readonly string ApiAuthScheme = "oauth ";
public AuthHandler(
IOptionsMonitor<AuthOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IPrimeApiProxy primeApiProxy,
IPrimeProxy primeProxy,
ApplicationSettingsProvider settings)
: base(options, logger, encoder, clock)
{
_primeApiProxy = primeApiProxy ?? throw new ArgumentNullException(nameof(primeApiProxy));
_primeProxy = primeProxy ?? throw new ArgumentNullException(nameof(primeProxy));
_adminGroupId = int.Parse((settings as dynamic).AdminGroupId as string);
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
Logger.LogTrace("HandleAuthenticateAsync");
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.Fail("Missing Authorization Header");
}
try
{
var token = FetchToken(Request);
if (string.IsNullOrWhiteSpace(token.token) && token.userId < 1)
{
Logger.LogError("Invalid token");
return AuthenticateResult.Fail("Invalid token");
}
var checkTokenResponse = await _primeProxy.CheckToken(token.token);
if (!string.IsNullOrWhiteSpace(checkTokenResponse.Error))
{
Logger.LogError(checkTokenResponse.Error);
return AuthenticateResult.Fail(checkTokenResponse.Error);
}
var isValidInt = int.TryParse(checkTokenResponse.UserId, out var userId);
if (!isValidInt)
{
return AuthenticateResult.Fail("User Id is invalid");
}
if (token.userId != userId)
{
return AuthenticateResult.Fail("The token belongs to another user");
}
bool isUserAdminAndCustomGroupMember = false;
if (checkTokenResponse.UserRole == "admin")
{
isUserAdminAndCustomGroupMember = await _primeApiProxy.IsGroupMember(token.token, token.userId, _adminGroupId);
}
var claims = new List<Claim>()
{
new Claim(ClaimTypes.NameIdentifier, token.userId.ToString()),
new Claim(ClaimTypes.Role, isUserAdminAndCustomGroupMember ? "Admin" : "Learner")
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
{
Logger.LogError(ex, "Auth exception");
return AuthenticateResult.Fail("Invalid Authorization Header");
}
}
private static (string token, int userId) FetchToken(HttpRequest request)
{
string authHeader = request.Headers[HeaderName];
if (authHeader != null && authHeader.StartsWith(ApiAuthScheme, StringComparison.OrdinalIgnoreCase))
{
string token = authHeader.Substring(ApiAuthScheme.Length).Trim();
string[] parts = token.Split(',', StringSplitOptions.RemoveEmptyEntries);
if (int.TryParse(parts[1], out int userId))
{
return (parts[0], userId);
}
}
return (null, 0);
}
}
I am developing web site with ASP.NET CORE that uses Claims for User Authentication and User Id and other infos keeps in Claims, is it security ?
ClaimsIdentity identity = new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Name, userInfo.Name),
new Claim(ClaimTypes.Surname, userInfo.Surname),
new Claim("Image", userInfo.Image),
new Claim(ClaimTypes.NameIdentifier,result.Id.ToString()),
new Claim(ClaimTypes.IsPersistent, loginViewModel.RememberMe.ToString())
},
CookieName.User);
HttpContext.SignOutAsync(CookieName.User).Wait();
HttpContext.SignInAsync(CookieName.User, new ClaimsPrincipal(identity),
new AuthenticationProperties
{
IsPersistent = loginViewModel.RememberMe,
AllowRefresh = true
}).Wait();
Sometime i need change user infos and it uses this. is it secure way ?
//Get
int id = int.Parse(new ClaimsCookie(HttpContext).GetValue(CookieName.User, KeyName.Id));
//Set Update
new ClaimsCookie(HttpContext).SetValue(CookieName.User, new[] { KeyName.Name, KeyName.Surname }, new[] { model.Name, model.Surname });
Class :
namespace ...
{
public class ClaimsCookie
{
private readonly HttpContext _httpContext;
public ClaimsCookie(HttpContext httpContext)
{
_httpContext = httpContext;
}
public string GetValue(string cookieName, string keyName)
{
var principal = _httpContext.User;
var cp = principal.Identities.First(i => i.AuthenticationType == cookieName.ToString());
return cp.FindFirst(keyName).Value;
}
public async void SetValue(string cookieName, string[] keyName, string[] value)
{
if (keyName.Length != value.Length)
{
return;
}
if (_httpContext == null)
return;
var principal = _httpContext.User;
var cp = principal.Identities.First(i => i.AuthenticationType == cookieName.ToString());
for (int i = 0; i < keyName.Length; i++)
{
if (cp.FindFirst(keyName[i]) != null)
{
cp.RemoveClaim(cp.FindFirst(keyName[i]));
cp.AddClaim(new Claim(keyName[i], value[i]));
}
}
await _httpContext.SignOutAsync(cookieName);
await _httpContext.SignInAsync(cookieName, new ClaimsPrincipal(cp),
new AuthenticationProperties
{
IsPersistent = bool.Parse(cp.FindFirst(KeyName.IsPersistent).Value),
AllowRefresh = true
});
}
public async void SetValue(string cookieName, string keyName, string value)
{
var principal = _httpContext.User;
var cp = principal.Identities.First(i => i.AuthenticationType == cookieName.ToString());
if (cp.FindFirst(keyName) != null)
{
cp.RemoveClaim(cp.FindFirst(keyName));
cp.AddClaim(new Claim(keyName, value));
}
await _httpContext.SignOutAsync(cookieName);
await _httpContext.SignInAsync(cookieName, new ClaimsPrincipal(cp),
new AuthenticationProperties
{
IsPersistent = bool.Parse(cp.FindFirst(KeyName.IsPersistent).Value),
AllowRefresh = true
});
}
}
public static class CookieName
{
public static string Company => "CompanyUserProfilCookie";
public static string User => "UserProfilCookie";
public static string Admin => "AdminPanelCookie";
}
public static class KeyName
{
public static string Id => ClaimTypes.NameIdentifier;
public static string Name => ClaimTypes.Name;
public static string Surname => ClaimTypes.Surname;
public static string IsPersistent => ClaimTypes.IsPersistent;
public static string Image => "Image";
}
}
I am setting HttpContext to this class from any Controller. Have any way static HttpContext, i dont want set from Controller ?
One option is to inject IHttpContextAccessor from DI and access HttpContext from it.
Change ClaimsCookie constructor to reflect that:
private readonly HttpContext _httpContext;
public ClaimCookie(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
Next you need to register both IHttpContextAccessor and ClaimCookie in Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddTransient<ClaimCookie>();
...rest of code ommited...
}
Then inject Your class and use is without providing HttpContext by yourself:
public class SomeController : Controller
{
private readonly ClaimCookie _claimCookie;
public SomeController(ClaimCookie claimCookie)
{
_claimCookie = claimCookie;
}
public async Task<IActionResult> SomeAction()
{
int id = int.Parse(_claimCookie.GetValue(CookieName.User, KeyName.Id));
await _claimCookie.SetValue(CookieName.User, new[] { KeyName.Name, KeyName.Surname }, new[] { model.Name, model.Surname });
...
}
Also read msdn's best practices in asynchronous programming why you should't use async void.
And about security (im not an expert) you also shouldn't store sensitive data in cookies, if You need so then store encrypted data.
I'm working on the securing the web API by OAuth bearer token as this working fine in local able to generate token, After hosting on server code is not working to generate token hosted on the server.
when am trying to the server sample Url "api.somedemo.com/token" showing the request responce as method not found ,
but in local "http://local:1234/token" same code working generated token.
I can't able find where am missing on the server to able generate bearer token.
here the code.
From Controller:
private BearerToken GetBearerToken(string userName, string password)
{
BearerToken token = null;
FormUrlEncodedContent bearerTokenContent = OAuthClientWrapper.CreateBearerToken(userName, password);
Uri tokenUri = new Uri("api.somedemo.com/token");
token = OAuthClientWrapper.GetJwtToken(tokenUri, bearerTokenContent);
return token;
}
OAuthClientWrapper Class:
public static FormUrlEncodedContent CreateBearerToken(string userName, string password)
{
var bearerTokenContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("username", userName),
new KeyValuePair<string, string>("password", password),
new KeyValuePair<string, string>("grant_type", "password")
};
return new FormUrlEncodedContent(bearerTokenContent);
}
GetJwtToken method:
public static BearerToken GetJwtToken(Uri uri, FormUrlEncodedContent bearerTokenContent)
{
BearerToken token = null;
try
{
using (var httpClient = new HttpClient())
{
//Set the headers
httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
using (HttpResponseMessage response = httpClient.PostAsync(uri, bearerTokenContent).Result)
{
if (response.IsSuccessStatusCode)
{
token = response.Content.ReadAsAsync<BearerToken>().Result;
}
else
{
var reasonPhrase = response.ReasonPhrase;
var result = response.Content.ReadAsStringAsync();
var errorMessage = string.Format("Error: {0} {1} for uri: {2}", reasonPhrase, result, uri.AbsoluteUri);
//Log the error message
token = null;
} //else
} //using
}//using
}//try
catch (AggregateException aex)
{
throw aex;
}//catch
return token;
}
As WebAPI Startup Class:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888
ConfigureAuth(app);
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseJwtBearerAuthentication(new MyJwtOptions());
}
}
Startupoverride :
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
int tokenExpiration = Convert.ToInt32(ConfigurationManager.AppSettings["TokenExpiration"]);
OAuthOptions = new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(),
//AccessTokenFormat = new JwtFormat(audience, new SymmetricKeyIssuerSecurityTokenProvider(issuer, signingKey)),
AccessTokenFormat = new MyJwtFormat(),
RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(tokenExpiration)
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Application OAuthProviderClass:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
IHttpClientWrapper _HttpClientWrapper = null;
private IEndPoints _ConfigsProviders = null;
public ApplicationOAuthProvider()
{
_HttpClientWrapper = new HttpClientWrapper();
_ConfigsProviders = new EndPoints();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
try
{
var isValidUser = false;
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var url = string.Format("{0}/{1}/{2}", "someAPIBaseUrl", context.UserName, context.Password);
//Getting user information if login is validated in json format.
var returnValue = _HttpClientWrapper.GetStringAsync(url);
var responseObject = JsonConvert.DeserializeObject<ResponseObject>(returnValue);
if (responseObject.Status==true)
{
isValidUser = true;
}
if (!isValidUser)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
context.Validated(identity);
}
catch (Exception ex)
{
throw ex;
}
}
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
public MyJwtFormat()
{
_tokenIssuer = ConfigurationManager.AppSettings["TokenIssuer"];
_tokenExpiration = Convert.ToInt32(ConfigurationManager.AppSettings["TokenExpiration"]);
}
ConfigurationManager.AppSettings["TokenIssuer"] given "localhost" in sever also.
Please help to fix this issue
Advance thanks you.
I've already implemented the basic Web API protection via IdentityServer4 based on this.
The demo is based on in-memory data. And most of tutorials are based on EF Core implementation for user data. As I searched there was a IUserService in IdentityServer3 which is now missing in version 4.
builder.AddInMemoryClients(Clients.Get());
builder.AddInMemoryScopes(Scopes.Get());
builder.AddInMemoryUsers(Users.Get());
How can I retrieve my user data from an EF6 store?
In Startup.cs, do this
builder.Services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
builder.Services.AddTransient<IProfileService, ProfileService>();
Here is a sample of ResourceOwnerPasswordValidator and ProfileService
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
private MyUserManager _myUserService { get; set; }
public ResourceOwnerPasswordValidator()
{
_myUserService = new MyUserManager();
}
public async Task<CustomGrantValidationResult> ValidateAsync(string userName, string password, ValidatedTokenRequest request)
{
var user = await _myUserService.FindByNameAsync(userName);
if (user != null && await _myUserService.CheckPasswordAsync(user, password))
{
return new CustomGrantValidationResult(user.EmailAddress, "password");
}
return new CustomGrantValidationResult("Invalid username or password");
}
}
public class ProfileService : IProfileService
{
MyUserManager _myUserManager;
public ProfileService()
{
_myUserManager = new MyUserManager();
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var sub = context.Subject.FindFirst("sub")?.Value;
if (sub != null)
{
var user = await _myUserManager.FindByIdAsync(sub);
var cp = await getClaims(user);
var claims = cp.Claims;
if (context.AllClaimsRequested == false ||
(context.RequestedClaimTypes != null && context.RequestedClaimTypes.Any()))
{
claims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToArray().AsEnumerable();
}
context.IssuedClaims = claims;
}
}
public Task IsActiveAsync(IsActiveContext context)
{
return Task.FromResult(0);
}
private async Task<ClaimsPrincipal> getClaims(CustomerSite user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = await _myUserManager.GetUserIdAsync(user);
var userName = await _myUserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity();
id.AddClaim(new Claim(JwtClaimTypes.Id, userId));
id.AddClaim(new Claim(JwtClaimTypes.PreferredUserName, userName));
var roles = await _myUserManager.GetRolesAsync(user);
foreach (var roleName in roles)
{
id.AddClaim(new Claim(JwtClaimTypes.Role, roleName));
}
id.AddClaims(await _myUserManager.GetClaimsAsync(user));
return new ClaimsPrincipal(id);
}
}