Why [Authorize] attribute return 401 status code JWT + Asp.net Web Api? - asp.net-web-api2

I'm having big trouble finding issue with the JWT token authentication with asp.net web api. This is first time I am dealing with JWT & Web Api authentication & Authorization.
I have implemented the following code.
Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new OAuthTokenProvider(),
RefreshTokenProvider = new RefreshTokenProvider(),
AccessTokenFormat = new Provider.JwtFormat("http://localhost:49860")
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
var issuer = "http://localhost:49860";
string audienceId = Config.AudienceId;
byte[] audienceSecret = TextEncodings.Base64Url.Decode(Config.AudienceSecret);
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
}
OAuthTokenProvider.cs
public class OAuthTokenProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// validate client credentials (demo)
// should be stored securely (salted, hashed, iterated)
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
/***Note: Add User validation business logic here**/
if (context.UserName != context.Password)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{ "as:client_id", "Kaushik Thanki" }
});
ClaimsIdentity oAuthIdentity = new ClaimsIdentity("JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, props);
context.Validated(ticket);
}
}
JwtFormat.cs
public class JwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public JwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = Config.AudienceId;
string symmetricKeyAsBase64 = Config.AudienceSecret;
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
RefreshTokenProvider.cs
public class RefreshTokenProvider : IAuthenticationTokenProvider
{
private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
public void Create(AuthenticationTokenCreateContext context)
{
throw new NotImplementedException();
}
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var guid = Guid.NewGuid().ToString();
// maybe only create a handle the first time, then re-use for same client
// copy properties and set the desired lifetime of refresh token
var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
{
IssuedUtc = context.Ticket.Properties.IssuedUtc,
ExpiresUtc = DateTime.UtcNow.AddYears(1)
};
var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
//_refreshTokens.TryAdd(guid, context.Ticket);
_refreshTokens.TryAdd(guid, refreshTokenTicket);
// consider storing only the hash of the handle
context.SetToken(guid);
}
public void Receive(AuthenticationTokenReceiveContext context)
{
throw new NotImplementedException();
}
public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
{
AuthenticationTicket ticket;
if (_refreshTokens.TryRemove(context.Token, out ticket))
{
context.SetTicket(ticket);
}
}
}
Now Once I pass the authentication (Which I kept dummy for initial level matching same username & password) & got the token & refresh token.
When I request for method that is decorated with [Authorize] attribute, I always gets 401 status code.
I testing this method in postman following way
Any help or guidance will be really appreciated. I have invested my two days finding the solution for this but all in vain.

Related

How to return a cookie from an ASP.NET Core Auth Cookie in a Controller

I currently have a razor page where I return a cookie and it works great. However, I am developing a SPA which uses VueJS so I have created an API to directly communicate with. I have converted my code from the Razor Page to the controller but I am not sure how I actually return the cookie when the user tries to log in. If there is a match I want it to return the cookie and create the cookie. As of now I get a 400 code as if this request is not working. Any thoughts what could be wrong with this?
public class LoginController : Controller
{
private readonly LoginDBContext _context;
private string connectionString;
public IConfiguration Configuration { get; }
public LoginController(LoginDBContext context, IConfiguration configuration)
{
_context = context;
connectionString = configuration["ConnectionStrings:MMCARMSContext"];
}
// GET: HomeController
public ActionResult Index()
{
return Ok(new { Result = "Test" });
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login([FromForm] string Username, [FromForm] string Password, [FromForm] bool RememberMe)
{
if (!String.IsNullOrEmpty(Username) && !String.IsNullOrEmpty(Password))
{
var users = _context.UsersAccountsTbl
.Where(a => a.Username == Username)
.Select(a => new { a.InternalUserNumber, a.Username, a.Password })
.ToArray();
if (users.Length == 1) //if we have more than 1 result we have security issue so do not allow login
{
var passwordHasher = new PasswordHasher<string>();
//To use you need to has with var hashedPassword = passwordHasher.HashPassword(UserName, Password);
//System.Diagnostics.Debug.WriteLine(passwordHasher.HashPassword("Username", "password"));
var user = users.First();
if (passwordHasher.VerifyHashedPassword(user.Username, user.Password, Password) == PasswordVerificationResult.Success)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.InternalUserNumber.ToString())
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
if (RememberMe)
{
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties
{
IsPersistent = RememberMe,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2)
});
}
else
{
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
}
return Ok(new { Result = "Cookie has been created!" });
}
else
{
return Ok(new { Result = "Password is incorrect!" });
}
}
return Ok(new { Result = "Username or Password does not exist!" });
}
else
{
return Ok(new { Result = "Username or Password invalid!" });
}
}
}
You could set the cookie in the HttpResponse, this way the cookie gets added when the client receives the response from your controller:
HttpCookie MyCookie = new HttpCookie("LastVisit");
DateTime now = DateTime.Now;
MyCookie.Value = now.ToString();
MyCookie.Expires = now.AddHours(1);
Response.Cookies.Add(MyCookie);
https://learn.microsoft.com/en-us/dotnet/api/system.web.httpresponse.cookies?view=netframework-4.8

Is there any way to do token base auth in asp.net core application without redundant stuffs?

Anyone have a good example to do token based authorization in asp.net core without this crap like IdentityContext and other? I just want to set up settings for token generating in order to my system can generate and check token in right way and I want to manage authentication process by myself. Thanks
Having used a solution from this article go.microsoft.com/fwlink/?linkid=84547:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.Authority = Configuration["AuthOptions:Authority"];
o.RequireHttpsMetadata = false;
o.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["AuthOptions:Issuer"],
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthOptions:Key"])),
ValidateLifetime = true,
};
});
services.AddMvc();
ConfigureDependincies(services);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
var configuration = new SlackConfiguration
{
WebhookUrl = new Uri("https://hooks.slack.com/services/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"),
MinLevel = LogLevel.Warning
};
loggerFactory.AddSlack(configuration, env);
// loggerFactory.AddDebug();
app.UseDefaultFiles();
app.UseDeveloperExceptionPage();
app.UseAuthentication();
//app.UseJwtBearerAuthentication(new JwtBearerOptions()
//{
// AutomaticAuthenticate = true,
// AutomaticChallenge = true,
// RequireHttpsMetadata = false,
// TokenValidationParameters = new TokenValidationParameters()
// {
// ValidIssuer = Configuration["AuthOptions:Issuer"],
// ValidateAudience = false,
// ValidateIssuerSigningKey = true,
// IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AuthOptions:Key"])),
// ValidateLifetime = true,
// }
//});
app.UseMvc();
}
var token = new JwtSecurityToken(
issuer: _root["AuthOptions:Issuer"],
notBefore: DateTime.UtcNow,
claims: identity.Claims,
expires: DateTime.UtcNow.Add(TimeSpan.FromMinutes(Convert.ToDouble(_root["AuthOptions:TokenLifeTime"]))),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(_root["AuthOptions:Key"])), SecurityAlgorithms.HmacSha512)
);
return token;
It's working for me.
It can be done in following way
Have a token generator API endpoint (controller) i.e. http://localhost/auth/token
In this, you generate token by verifying the authenticate user (check user against store)
The generated token can be validated by providing authentication schema in ASP.NET Core pipeline. app.AddAuthentication()
Any subsequent calls to API, should have Authorization header with generated token.
This approach can be refined accordingly.
I've done this very thing. I got sick of all the third party things so I wrote my own.
You want to create tokens and provide them/ validate them through an api.
Here is an example of the api controller that creates the token initially.
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly TokenCreatorOption _tco;
private readonly CryptoHash _ch;
public TokenController(IOptions<TokenCreatorOption> ioptTCO, IOptions<CryptoHash> ioptCH, IOptions<ConnectionStrings> ioptConn)
{
_tco = ioptTCO.Value;
_ch = ioptCH.Value;
}
[HttpPost("")]
public async Task<IActionResult> IssueToken([FromBody] CredentialUser model)
{
///if model is null, this is an incorrect format
if(model == null)
{
return BadRequest();
}
var user = GetUserFromDatabaseOrStore(model.userName, model.passWord);
if(user == null)
{
return NotFound();
}
TokenCreatorOption newTCO = _tco; ///get your initial instantiation of the TokenCreatorOption. This is set to default values based off appsettings or in configure services
newTCO.UserObject = user;
newTCO.Expiration = DateTime.UtcNow.AddMinutes(30).ToString("yyyy-MM-dd hh:mm:ss.ss tt");
///anything within the TokenCreatorOption will be hashed, anything in the token Provider is not going to be hashed (not secured), but acts as a good object to store just general things that are needed on client side.
TokenProvider _tpo = new TokenProvider();
_tpo.tco = TokenInteraction.CreateToken(newTCO, _ch.salt);
_tpo.listApp = xapp; ///put anything you wouldn't want to be hashed and claimed against outside of the object. so you always validate things inside the tco, but never exclude anything inside tco. This was a fatal flaw in tokens in the past.
///this is using messagepack to serialize, to make it smaller since this is going to be passed between every request/response. Consider zipping as well if large enough.
var serializer = MessagePackSerializer.Get<TokenProvider>();
byte[] obj = null;
using (var byteStream = new MemoryStream())
{
serializer.Pack(byteStream, _tpo);
obj = byteStream.ToArray();
}
return File(obj, "application/octet-stream");
}
TokenCreatorOption Class
public class TokenCreatorOption
{
public string Issuer { get; set; }
public UserFromThatDatabaseOrStore UserObject { get; set; }
public string Expiration { get; set; }
public string HashValue { get; set; }
}
Notice that all these objects in TokenCreatorOption are claims. Every single one is checked in the hash function.
Here is the Token Creator and the Token Validator, once a token is valid, you can reissue a new one.
TokenInteraction
public static class TokenInteraction
{
public static TokenCreatorOption CreateToken(TokenCreatorOption _tco, byte[] salt)
{
byte[] exp = Encoding.UTF8.GetBytes(_tco.Expiration);
byte[] issuer = Encoding.UTF8.GetBytes(_tco.Issuer);
byte[] user = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_tco.UserObject));
byte[] salty = salt;
IEnumerable<byte> rv = exp.Concat(issuer).Concat(user).Concat(salty);
HashAlgorithm alg = SHA512.Create();
_tco.HashValue = Convert.ToBase64String(alg.ComputeHash(rv.ToArray()));
return _tco;
}
public static bool ValidateToken(TokenCreatorOption _tco, byte[] salt)
{
byte[] exp = Encoding.UTF8.GetBytes(_tco.Expiration);
byte[] issuer = Encoding.UTF8.GetBytes(_tco.Issuer);
byte[] user = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(_tco.UserObject));
byte[] salty = salt;
IEnumerable<byte> rv = exp.Concat(issuer).Concat(user).Concat(salty);
HashAlgorithm alg = SHA512.Create();
if (_tco.HashValue != Convert.ToBase64String(alg.ComputeHash(rv.ToArray())))
{
return false;
}
else
{
return true;
}
}
Notice in TokenInteraction The order of bytes added to rv needs to be in the same order when we validate the token.
Now we can have a validate controller.
[Route("api/[controller]")]
public class ValidateController : Controller
{
private readonly TokenCreatorOption _tco;
private readonly CryptoHash _ch;
public ValidateController(IOptions<TokenCreatorOption> ioptTCO, IOptions<CryptoHash> ioptCH)
{
_tco = ioptTCO.Value;
_ch = ioptCH.Value;
}
[HttpPost("")]
public async Task<IActionResult> ValidateToken([FromBody] TokenCreatorOption model)
{
if (model == null)
{
return BadRequest("Model Cannot be Null");
}
///Kick them right now if session is expired, so we don't have to do the full hashing.
if (DateTime.ParseExact(model.Expiration, "yyyy-MM-dd hh:mm:ss.ss tt", CultureInfo.InvariantCulture) < DateTime.UtcNow)
{
return BadRequest("Expired Datetime");
}
if(!TokenInteraction.ValidateToken(model, _ch.salt))
{
return Unauthorized();
}
model.Expiration = DateTime.UtcNow.AddMinutes(30).ToString("yyyy-MM-dd hh:mm:ss.ss tt");
TokenProvider _tpo = new TokenProvider();
_tpo.tco = TokenInteraction.CreateToken(model, _ch.salt);
var serializer = MessagePackSerializer.Get<TokenProvider>();
byte[] obj = null;
using (var byteStream = new MemoryStream())
{
serializer.Pack(byteStream, _tpo);
obj = byteStream.ToArray();
}
return File(obj, "application/octet-stream");
}
And of course when you are initially registering the services. Create your salt value either through a cert, or through a random crypto number generator.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddCors();
services.AddOptions();
services.AddSwaggerGen();
services.ConfigureSwaggerGen(options =>
{
options.SingleApiVersion(new Swashbuckle.Swagger.Model.Info
{
Version = "v1"
});
services.Configure<TokenCreatorOption>(myopt =>
{
myopt.Issuer = "Issuer"; //either from appsettings or manually
myopt.Expiration = null;
myopt.UserObject = null;
myopt.HashValue = "";
});
byte[] salty;
new RNGCryptoServiceProvider().GetBytes(salty = new byte[64]);
services.Configure<CryptoHash>(copt =>
{
copt.salt = (new Rfc2898DeriveBytes("Super!SecretKey!123456789!##$", salty, 1000)).GetBytes(64);
});
services.AddSingleton<IConfiguration>(Configuration);

Specify login_hint using .NET Google.Apis.Oauth2.v2

Since the Google.Apis.Oauth2.v2 in GoogleWebAuthorizationBroker.AuthorizeAsync() requests a uri to a static json file that contains the links and parameters to Google Oauth2 services, how can I specify the login_hint parameter if I happen to know that information ahead of time?
var credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/client_secret.json"),
myScopes,
"user",
CancellationToken.None);
Extract from client_secret.json:
"auth_uri":"https://accounts.google.com/o/oauth2/auth?login_hint=user#domain.com"
How to specify the login_hint parameteron a per user basis?
I ended up subclassing the Google web authorization broker like this:
public class MyOAuth2WebAuthorizationBroker : GoogleWebAuthorizationBroker
{
public static async Task<UserCredential> AuthorizeAsync(ClientSecrets clientSecrets,
IEnumerable<string> scopes, string user, CancellationToken taskCancellationToken)
{
var initializer = new MyOAuth2AuthorizationCodeFlow.Initializer
{
ClientSecrets = clientSecrets,
Scopes = scopes,
DataStore = new StorageDataStore(),
};
var installedApp = new AuthorizationCodeWindowsInstalledApp(new MyOAuth2AuthorizationCodeFlow(initializer, user));
return await installedApp.AuthorizeAsync(user, taskCancellationToken).ConfigureAwait(false);
}
public class MyOAuth2AuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
private string loginHint { get; set; }
public MyOAuth2AuthorizationCodeFlow(Initializer initializer, string loginHint) : base(initializer)
{
this.loginHint = loginHint;
}
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
{
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
LoginHint = this.loginHint
};
}
}
}

WebAPI : How to add the Account / Authentication logic to a self hosted WebAPI service

I just came across a great reference example of using authenticated WebAPI with AngularJS:
http://www.codeproject.com/Articles/742532/Using-Web-API-Individual-User-Account-plus-CORS-En?msg=4841205#xx4841205xx
An ideal solution for me would be to have such WebAPI service self hosted instead of running it as a Web application.
I just do not know where to place all of the authentication / authorization logic within a self hosted (OWIN / Topshelf) solution.
For example, in the Web app, we have these two files: Startup.Auth, and ApplicationOAuthProvider:
Startup.Auth:
public partial class Startup
{
static Startup()
{
PublicClientId = "self";
UserManagerFactory = () => new UserManager<IdentityUser>(new UserStore<IdentityUser>());
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId, UserManagerFactory),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = true
};
}
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static Func<UserManager<IdentityUser>> UserManagerFactory { get; set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
}
ApplicationOAuthProvider:
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
private readonly Func<UserManager<IdentityUser>> _userManagerFactory;
public ApplicationOAuthProvider(string publicClientId, Func<UserManager<IdentityUser>> userManagerFactory)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
if (userManagerFactory == null)
{
throw new ArgumentNullException("userManagerFactory");
}
_publicClientId = publicClientId;
_userManagerFactory = userManagerFactory;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Add Access-Control-Allow-Origin header as Enabling the Web API CORS will not enable it for this provider request.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (UserManager<IdentityUser> userManager = _userManagerFactory())
{
IdentityUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await userManager.CreateIdentityAsync(user,
context.Options.AuthenticationType);
ClaimsIdentity cookiesIdentity = await userManager.CreateIdentityAsync(user,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
}
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 ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
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);
}
}
I'm looking for a way to integrate these into my OWIN self hosted app, and have these authentication features. start upon application startup, and function as they do in the Web app version.

ServiceStack Authenticate attribute does not checking if user is authenticated

Im trying to make a service can only be accessed if the client is authenticated and I put the Authenticate attribute but it did not work because when I can access the service without being authenticated. I placed the Authenticate attribute before the Request DTO , top of a service and before the Action. Here is some code of the service I want to secure
[Authenticate]
public class HelloService : Service
{
public const string HelloServiceCounterKey = "HelloServiceCounter";
public object Any(HelloRequest request)
{
var userSession = SessionAs<AppHost.CustomUserSession>();
Session.Set(HelloServiceCounterKey, Session.Get<int>(HelloServiceCounterKey) + 1);
var roles = string.Join(", ", userSession.Roles.ToArray());
return new HelloResponse { Result = "Hello, " + request.Name + ", your role(s): " + roles };
}
}
I have this in my AppHost Configure(Funq.Container container)
Plugins.Add(new AuthFeature(
() => new CustomUserSession(),
new[] { new CustomCredentialsAuthProvider() }
));
public class CustomUserSession : AuthUserSession
{
public string CompanyName { get; set; }
}
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
if (!Membership.ValidateUser(userName, password)) return false;
var session = (CustomUserSession)authService.GetSession(false);
session.CompanyName = "Company from DB";
session.UserAuthId = userName;
session.IsAuthenticated = true;
// add roles
session.Roles = new List<string>();
if (session.UserAuthId == "admin") session.Roles.Add(RoleNames.Admin);
session.Roles.Add("User");
return true;
}
}
When access the service in the line of
var roles = string.Join(", ", userSession.Roles.ToArray());
obviosly return NULL because have not been autheticated.
What should do Authenticate attribute on this case?
you need to configure your authentication provider in you app host configuration as follows:
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[]
{
your providers here...
}));
}
Edit:
Assuming that CustomUserSession inherits from IAuthSession you can change
var session = (CustomUserSession)authService.GetSession(false);
To
var session = authService.GetSession<CustomUserSession>();
and as far as I see you aren't saving the session after the authentication
try something like this:
public override object Authenticate(IServiceBase authService, IAuthSession session, ServiceStack.ServiceInterface.Auth.Auth request)
{
string userName = request.UserName;
string password = request.Password;
if (!LoginMatchesSession(session, userName))
{
authService.RemoveSession();
session = authService.GetSession();
}
if (TryAuthenticate(authService, userName, password))
{
authService.SaveSession(session, SessionExpiry);
if (session.UserAuthName == null)
session.UserAuthName = userName;
OnAuthenticated(authService, session, null, null);
return new AuthResponse
{
UserName = userName,
SessionId = session.Id,
ReferrerUrl = RedirectUrl
};
}
throw new HttpError(HttpStatusCode.BadRequest, "400", "wrong credentials");
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var session = authService.GetSession<CustomUserSession>();
if (!Membership.ValidateUser(userName, password)) return false;
session.IsAuthenticated = true;
session.Id = authService.GetSessionId();
return true;
}
EDIT:
there's other missing part you need to configure a cacheclient since all the session are managed in cache
try something like that:
container.Register<ICacheClient>(new MemoryCacheClient(){FlushOnDispose = false});
can you update your code with your host configuration?