I'm having some trouble with authenticating with ServiceStack using the BasicAuthProvider. All works well when I authenticate using the provider route 'auth/myauth' but when I go to one of my other service DTOS that use the [Authenticate] attribute e.g. /hello, I always get a 401 Unauthorized error even when I always supply the basic authentication details in the 'Authorization' header using beforeSend with jQuery.
Basically, I'm building an API for a mobile app that involves credential authentication on the first time(or if a supplied token isn't expired), then subsequently basic authentication of supplied token for other requests. I'm trying to authenticate every request, as described here. Also here. Here's my code:
Custom Provider
public class MyAuthProvider : BasicAuthProvider
{
public new static string Name = "MyAuth";
public new static string Realm = "/auth/myauth";
public MyAuthProvider()
{
this.Provider = Name;
this.AuthRealm = Realm;
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var httpReq = authService.RequestContext.Get<IHttpRequest>();
var basicAuth = httpReq.GetBasicAuthUserAndPassword();
if (basicAuth == null)
throw HttpError.Unauthorized("Invalid BasicAuth credentials");
var us = basicAuth.Value.Key;
var ps = basicAuth.Value.Value;
if (ps == "password")
{
return true;
}
return false;
}
}
Service
public class HelloService : Service
{
//handle OPTIONS in preflight - http://joeriks.com/2013/01/12/cors-basicauth-on-servicestack-with-custom-authentication/
public object Options(Hello request) { return true; }
[Authenticate("MyAuth")]
public object Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
[Authenticate("MyAuth")]
public object Get(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
Configure Method
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[] {
new MyAuthProvider()
}));
//register any dependencies your services use, e.g:
container.Register<ICacheClient>(new MemoryCacheClient() { FlushOnDispose = false });
//set endpoint information
SetConfig(new EndpointHostConfig
{
GlobalResponseHeaders =
{
{"Access-Control-Allow-Origin","http://localhost"},
{"Access-Control-Allow-Methods","GET, POST, PUT, DELETE, OPTIONS"},
{"Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Origin" }
},
});
}
This works
function make_base_auth(user, password) {
var tok = user + ':' + password;
var hash = btoa(tok);
return "Basic " + hash;
}
////
$.ajax({
url: 'http://localhost:61750/auth/myauth?format=json',
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", make_base_auth("id#email.com","password"));
}
}).done(function (data) {
if( console && console.log ) {
console.log("Sample of data:", data);
}
});
But this doesn't
$.ajax({
url: 'http://localhost:61750/hello?format=json',
data: { Name:"Foo" },
type: 'POST',
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", make_base_auth("id#email","password"));
}
}).done(function (data) {
if( console && console.log ) {
console.log("Sample of data:", data);
}
});
Thanks for your help.
I had to create a custom authenticate attribute with guidance from this gist -> https://gist.github.com/joeriks/4518393
In the AuthenticateIfBasicAuth method, I set provider to use MyAuthProvider.Name
Then,
[CustomAuthenticate]
public object Post(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
Related
Before download the file, user need to enter the password. So I want to show the message if password is correct and in the same time start the download the file to the user.
public async Task<IActionResult> OnPostAsync()
{
var getFileUpload = await _context.FileUpload.FirstAsync(c => c.Guid == Guid && c.ExpiredOn.HasValue);
if (!ModelState.IsValid)
{
var message = string.Join(" | ", ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage));
return BadRequest(message);
}
if (DateTime.Today > getFileUpload.ExpiredOn.Value.AddDays(1))
{
Exception = "File already expired. Please ask administrator to share again";
return Page();
}
try
{
bool verified = BCrypt.Net.BCrypt.Verify(Password, getFileUpload.PasswordHash);
if (!verified)
{
Exception = "Password is wrong, please enter correct password";
return Page();
}
byte[] fileBytes = System.IO.File.ReadAllBytes(getFileUpload.Path);
var fileName = getFileUpload.FileName;
File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Success = true;
return Page();
}
catch
{
Exception = "Failed to download the data";
return Page();
}
}
I can see the message, but file cannot download.
But when I change return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);, file able to download but cannot not alert the message.
Any idea how I can fix this?
You can verify the password with ajax first, and get the file after success. This is the pagemodel code.
public async Task<IActionResult> OnPostAsync()
{
//other code
if (true)
{
return new JsonResult("success");
}
else
{
return BadRequest();
}
}
public IActionResult OnGetFileAsync()
{
//get file from header
StringValues filename;
Request.Headers.TryGetValue("filename", out filename);
var stream = System.IO.File.OpenRead("file path");
string fileExt = Path.GetExtension("1.png");
var provider = new FileExtensionContentTypeProvider();
var memi = provider.Mappings[fileExt];
return File(stream, memi, Path.GetFileName("filename"));
}
Ajax in the page.
function verify() {
$.ajax({
url: '/?handler',
method: 'post',
headers: {
RequestVerificationToken: $('input:hidden[name="__RequestVerificationToken"]').val()
},
success: function (data,status) {
fetch("/?handler=file", {
//Write the filename to be obtained into the http header
headers: {
'filename': data
}
}).then(res => res.blob().then(blob => {
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
var filename = res.headers.get('content-disposition').split(';')[1].split('=')[1]
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}));
//set successful message
},
error: function () {
console.log('e')
//set the error message in the page
}
})
}
I am trying to implement OAuth2 ClientCredentials flow on ASP.NET CORE 3.1. I follow guidelines on the official GitHub repo.
The problem is regarding getting bearer token on Swagger-UI. It doesn't pass automatically. I investigated the issue on Swashbuckle Github repo. There are some closed issues. There's no solution.
Below my implementation :
public static class SwaggerExtensions
{
public static IServiceCollection EnableSwagger(this IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Sample API", Version = "v1" });
c.AddSecurityDefinition("oauth2",
new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
ClientCredentials = new OpenApiOAuthFlow
{
TokenUrl = new Uri("/api/auth/token", UriKind.Relative),
Scopes = new Dictionary<string, string>
{
{ "readAccess", "Access read operations" },
{ "writeAccess", "Access write operations" }
},
}
},
Name = "Authorization",
In = ParameterLocation.Header,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }, Name = "oauth2"
},
new[] { "readAccess", "writeAccess" }
}
});
c.OperationFilter<OAuth2OperationFilter>();
});
return services;
}
public class AuthenticationAttribute : Attribute, IAsyncAuthorizationFilter
{
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
bool isAnonymousAllowed = context.Filters.Any(f => f.GetType() == typeof(AllowAnonymousFilter));
if (isAnonymousAllowed)
{
return;
}
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authHeaderValue))
{
throw new UnauthorizedAccessException("A valid key must be supplied");
}
string authHeader = authHeaderValue.ToString();
if (string.IsNullOrEmpty(authHeader))
....
}
}
After creating an access_token, I cannot get this token on another controller on Swagger-UI.
Swashbuckle.AspNetCore version -> 5.5.1
Swashbuckle.AspNetCore.Swagger -> 5.5.1
The code below, which is summarized for better understanding, works perfectly with LOCALHOST, however, when I put it in IIS, the body of the request always arrives EMPTY. Can someone help me?
Client application code:
login(userName: string, password: string): Observable<User> {
const headers = new HttpHeaders({
'Content-Type': 'application/json'
});
return this.http.post(`${environment.API_URL}/profiles/login`,
{ userName, password }, { headers }
).pipe(
tap((currentUser: User) => {
this.updateUser(currentUser)
.then(
() => {
console.log('currentUser login stored: ', AppSettings.currentUser);
},
error => console.error('Error storing currentUser login', error)
);
return AppSettings.currentUser;
}),
);
}
ASP.NET Core 3.1 application code on the server:
[Route("api/[controller]")]
[ApiController]
public class ProfilesController : ControllerBase
{
[HttpPost("login")]
public async Task<ActionResult> Login(LoginRequest request)
{
try
{
using (var Reader = new StreamReader(Request.Body, Encoding.UTF8))
{
var sb = new StringBuilder();
sb.AppendFormat("ContentType: {0}\n", Request.ContentType);
sb.AppendFormat("Request: {0}\n", Request.ToString());
sb.AppendFormat("ContentLength: {0}\n", Request.ContentLength.ToString());
if (Request.IsHttps)
sb.AppendFormat("{0}\n", "HTTPS!");
var headers = String.Empty;
foreach (var key in Request.Headers)
headers += key.Key + "=" + key.Value + Environment.NewLine;
sb.AppendFormat("Headers: \n{0}\n", headers);
sb.AppendFormat("QueryString: {0}\n", Request.QueryString);
var text = await Reader.ReadToEndAsync();
sb.AppendFormat("Body: {0}\n", text);
return Ok(sb.ToString());
}
return Ok("OK");
}
catch (System.Exception ex)
{
return Unauthorized($"{ex.Message}: {ex.StackTrace}");
}
}
}
Request result:
ContentType: application/json
Request: Microsoft.AspNetCore.Http.DefaultHttpRequest
ContentLength: 79
Headers:
Accept=*/*
Accept-Encoding=gzip, deflate, br
Cache-Control=no-cache
Connection=keep-alive
Content-Length=79
Content-Type=application/json
Host=loja.online
User-Agent=PostmanRuntime/7.22.0
Postman-Token=121f1927-c340-492f-a98b-0d6586ff32d8
QueryString:
Body:
Using POSTMAN the same thing happens!
Try Specifying the source:
public async Task<ActionResult> Login([FromBody] LoginRequest request) //Added [FromBody]
Just for Further Details
Today I create some Login Page with Session to make dynamic menu base on database, the web working fine but if I open specified page like localhost/Student/List before login I throw in to login page and after I'm login the page redirect me into localhost/Home/Index who is the default redirect after login page.
My question is how can i enter the specific address after i login? like using Identity.
I'm Sory I will update my Question.
I'm Using Ajax for my login page, if the result of ajax is true then i will redirect to Home/Index.
That's My Validate Function Login cshtml js
function Validate() {
if ($("#username").val() == null || $("#username").val() == "") {
$("#message").text("User Name Required!");
}
else if ($("#password").val() == null || $("#password").val() == "") {
$("#message").text("Password Required!");
}
else{
$.ajax({
type: "POST",
url: '#Url.Action("SignIn", "Account")',
data: {
username : $('#username').val(),
password : $('#password').val()
},
error: function (result) {
$("#message").text("There is a Problem, Try Again!");
},
success: function (result) {
console.log(result);
if (result.status == true) {
window.location.href = '#Url.Action("Index", "Home")';
}
else {
$("#message").text(result.message);
}
}
});
}
}
nah, you can see the window.location.href.
and then my sign in controller
public async Task<IActionResult> SignIn(LoginModel model)
{
var UserLogin = _dbContext.AspNetUsers.Where(a => a.UserName == model.username).FirstOrDefault();
if(UserLogin != null)
{
if(UserLogin.EmailConfirmed == true)
{
var result = await _signInManager.PasswordSignInAsync(model.username, model.password, lockoutOnFailure: false, isPersistent: false);
if (result.Succeeded)
{
HttpContext.Session.SetString("email", UserLogin.Email);
HttpContext.Session.SetString("username", UserLogin.UserName);
HttpContext.Session.SetString("id", UserLogin.Id);
HttpContext.Session.SetString("roleId", UserLogin.RolesId);
int roleId = Convert.ToInt32(Convert.ToDouble(HttpContext.Session.GetString("roleId")));
List<Menus> menus = _dbContext.LinkRolesMenu.Where(a => a.RolesId == roleId).Select(a => a.Menus).ToList();
DataSet ds = new DataSet();
ds = ToDataSet(menus);
DataTable table = ds.Tables[0];
DataRow[] parentMenus = table.Select("ParentId = 0");
var sb = new StringBuilder();
string menuString = GenerateUL(parentMenus, table, sb);
HttpContext.Session.SetString("menuString", menuString);
HttpContext.Session.SetString("menus", JsonConvert.SerializeObject(menus));
return Json(new { status = true, message = "Login Successfull!" });
}
else
{
return Json(new { status = false, message = "Invalid Password!" });
}
}
else
{
return Json(new { status = false, message = "Email Not Confirmed!" });
}
}
else
{
return Json(new { status = false, message = "Invalid UserName!" });
}
}
nah, that's the return return Json(new { status = true, message = "Login Successfull!" });
how can i return that into specific page
I open specified page like localhost/Student/List before login I throw in to login page
In my opinion, the only point is that you need to pass the current url (Student/List) as parameter to your login page.Refer to my demo using Custom Authorize Filter
1.Custom Authorize Filter: judge whether use has already logged in,if not, redirect to login page
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;
public class MyAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
//your judgement to if user has logged in
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
//redirect to Account/Login
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new
{
controller = "Account",
action = "SignIn",
returnUrl = filterContext.HttpContext.Request.Path.ToUriComponent()
}));
}
}
}
2. StudentController:
[MyAuthorizeAttribute]
public class StudentController: Controller
3.AccountController
public IActionResult SignIn(string returnUrl = null)
{
// receive returnUrl and pass to View
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost]
public async Task<IActionResult> SignIn(LoginModel model, string returnUrl = null)
{
//logic to login
return Json(new { status = true, message = "Login Successfull!" ,returnUrl = returnUrl });
}
4.SignIn.cshtml:redirect to returnUrl in success callback function
$.ajax({
type: "POST",
url: '#Url.Action("SignIn", "Account")',
data: {
username : $('#username').val(),
password : $('#password').val(),
returnUrl: '#ViewBag.ReturnUrl'
},
error: function (result) {
$("#message").text("There is a Problem, Try Again!");
},
success: function (result) {
console.log(result);
if (result.status == true) {
window.location.href = result.returnUrl;
}
else {
$("#message").text(result.message);
}
}
});
I'm fairly new to token based authentication and I have a problem of how to maintain login state after I login.
I want to create a SPA website for which I am using Knockoutjs for my front end and SammyJS for routing and changing the views.
After I login in and get the token I store it in localStorage and set the username into an observable which I am displaying.
My problem is that after I close the tab or browser and I go back to the site, the token is in the localStorage but I can't see the user logged in.
I want to maintain the login state until the token expires. My question is what should I do with the token from the localStorage when I enter the site in order to maintain the login state of that user?
Do I need to make something in the startup class or to check if that user exists in the DB?
Thanks in advance!
Here is my code:
StartupAuth.cs
[assembly: OwinStartup(typeof(EventHub.PL.WebUI.Startup))] namespace EventHub.PL.WebUI {
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get;private set; }
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
public const string TokenEndpointPath = "/api/token";
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)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
// 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);
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString(TokenEndpointPath),
Provider = new ApplicationOAuthProvider(PublicClientId),
//AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
// In production mode set AllowInsecureHttp = false
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
//app.UseOAuthBearerTokens( OAuthOptions );
app.UseOAuthAuthorizationServer(OAuthOptions);
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
}
}
AccountController.cs
[HttpPost]
[AllowAnonymous]
[Route("Login")]
public async Task<IHttpActionResult> Login(LoginUser model)
{
var request = HttpContext.Current.Request;
var tokenServiceUrl = request.Url.GetLeftPart(UriPartial.Authority) + request.ApplicationPath + "/api/Token";
using (var client = new HttpClient())
{
var requestParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", model.Email),
new KeyValuePair<string, string>("password", model.Password)
};
var requestParamsFormUrlEncoded = new FormUrlEncodedContent(requestParams);
var tokenServiceResponse = await client.PostAsync(tokenServiceUrl, requestParamsFormUrlEncoded);
var responseString = await tokenServiceResponse.Content.ReadAsStringAsync();
var json = JsonConvert.DeserializeObject<TokenResponse>(responseString);
var responseCode = tokenServiceResponse.StatusCode;
if (responseCode == HttpStatusCode.OK)
{
RegisterUser user = userRepository.GetNameById(json.Id);
var data = new
{
status = "success",
json.access_token,
user.Lastname
};
return Json(data);
}
return Json(new { status = "failed" });
}
}
here is the KO part:
var LoginApp = function () {
var instance = this;
instance.mainViewModel = new MainViewModel();
instance.loginViewModel = new LoginViewModel();
instance.loginRepository = new LoginRepository();
instance.loginViewModel.signIn = function() {
$('.loader-header').show();
var postData = {
email: instance.loginViewModel.email(),
password: instance.loginViewModel.password
}
instance.loginRepository.SignIn(SignInSuccess, postData);
};
instance.SignInSuccess = function(response) {
if (response.status === 'success') {
instance.mainViewModel.username(response.Lastname);
instance.mainViewModel.isVisible(true);
var userData = {
token: response.access_token,
username: response.Lastname
};
localStorage.setItem('AuthorizationData', JSON.stringify(userData));
$('.loader-header').hide();
dialog.close();
} else {
$('.loader-header').hide();
}
};
instance.init = function () {
ko.applyBindings(instance.loginViewModel, document.getElementById("signin-form"));
ko.applyBindings(instance.mainViewModel, document.getElementById("main-wrapper"));
}
instance.init();
}
$(document).ready(function () {
var loginApp = LoginApp();
});
UPDATE
here is my routing also
var appRoot = root;
(function ($) {
var app = $.sammy('#page', function () {
this.get('#/home', function (context) {
document.title = 'Home - ' + title;
var url = getUrlFromHash(context.path);
loadView(url, new MainViewModel(), MainApp);
//context.load(url).swap();
});
this.get('#/about', function (context) {
var url = getUrlFromHash(context.path);
loadView(url, new AboutViewModel(), AboutApp);
});
this.get('#/manage', function (context) {
var url = getUrlFromHash(context.path);
loadView(url, new AboutViewModel(), AboutApp);
});
});
$(function () {
app.run('#/home');
});
})(jQuery);
function loadView(url, viewModel, callback) {
$.get(url, function (response) {
var $container = $('#page');
//var $view = $('#page').html(response);
$container.html(response);
callback();
});
}
function getUrlFromHash(hash) {
var url = hash.replace('#/', '');
if (url === appRoot)
url = 'home';
return url;
}
Right now all you're doing is storing the user's credentials in localStorage but not using them to perform authorization. One alternative is to use the Sammy.OAuth2 plugin (which you can find it here).
You can define a route to make the authentication like:
app.post("#/oauth/login", function(context) {
this.load('http://yourwebsite/login',
{
cache: false,
type: 'post',
data: {
email: $("input[name=email]").val(),
password: $("input[name=password]").val()
}
})
.then(function(content) {
if(content != false){
if(app.getAccessToken() == null){
app.setAccessToken(token());
}
}else{
app.trigger("oauth.denied");
return false;
}
});
});
In 'protected' routes you can check if the user is already logged in like this:
app.get("#/profile", function(context) {
if(app.getAccessToken() != null)
context.render('view/profile.template');
else
this.requireOAuth();
});
This examples will have to be modified to populate the token according to your scenario. Here's a complete tutorial on Sammy.Oath2.