I had implemented a JWT token in my web API project with roles management.its working fine. Authorization attributes also working well. Role Management also implemented with the JWT.
Here is the Controller side Code.
public Object Authentication(string objuser, string password)
{
var Login = UserLogin.DoLogin(objuser, password);
if (Login!=null)
{
if (Login.Email== "Sucess")
{
string UserRole = GetUserRole(Login.UserType);**//Admin,Buyer,Seller**
string issuer = ConfigurationManager.AppSettings["Url"].ToString();
var key = ConfigurationManager.AppSettings["AuthTokenKey"].ToString();
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var permClaims = new List<Claim>();
permClaims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()));
permClaims.Add(new Claim("valid", "1"));
permClaims.Add(new Claim("userid", Login.User_ID.ToString()));
permClaims.Add(new Claim("Email", Login.Email));
permClaims.Add(new Claim(ClaimTypes.Role, UserRole));
var token = new JwtSecurityToken(issuer,
issuer,
permClaims,
expires: DateTime.Now.AddDays(1),
signingCredentials: credentials);
var jwt_token = new JwtSecurityTokenHandler().WriteToken(token);
Login.Token = jwt_token;
return Login;
}
}
return Login;
}
The startup file code is here
public void Configuration(IAppBuilder app)
{
//createRolesandUsers();
string Url=ConfigurationManager.AppSettings["Url"].ToString();
string key = ConfigurationManager.AppSettings["AuthTokenKey"].ToString();
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Url, //some string, normally web url,
ValidAudience = Url,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
}
});
}
Ajax side code
I am sending this token from ajax like this way:
$.ajax({
url: pathtoBuyerDetails,
type: "Get",
**headers: { Authorization: 'Bearer ' + sessionStorage.getItem('AuthorizeToken')},**
data: { Time: Time, UID: UID },
success: function (values) {something}
The new requirement from my client is that the token should be validated with UserID. i.e
first Buyer login and his UserID=2 and TokenID="eyJqdGkiOiJkNTEwMzEwMC1jZDI3LTQxY2QtOTFmZS1iZGFjOTY5ZTMwOTUiLCJ2YWxpZCI6IjEiLCJleHAiOjE2MDExMjI4NTksImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTUyNTMvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1NTI1My8ifQ"
Second Buyer login and his UserID=3 and TokenID="JqdGkiOiJkNTEwMzEwMC1jZDI3LTQxY2QtOTFmZS1iZGFjOTY5ZTMwOTUiLCJ2YWxpZCI6IjEiLCJleHAiOjE2MDExMjI4NTksImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6NTUyNTMvIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1NTI1My8ifQ.zEazMtGdWSjMOj4EqrCM3BX"
Required Solution
if 2nd Buyer changes his id from 3 to 2 in session-storage and hit the URL he should not access the data of First Buyer because its token id does not match the first buyer token id.
In short, we need to ensure that a buyer cannot access another buyer’s data. This means we need to check the JWT to ensure that the user ID we passed as the parameter is the same as the owner of the JWT.
Related
I work in a corporate and we have got an AAD for all the users in our organisation.
I am working on creating an app that authenticates users with AAD using OpenIdConnect. Now, I have followed this sample from Azure AD B2C. it works fine when I run it locally for the first time (after an hour break) and OnAuthorizationCodeReceived is triggered, however, when I stop and restart the app in visual studio OnAuthorizationCodeReceived doesn't get triggered and as a result, I get a null User (IAccount) when trying to retrieve token silently in this code block
public async Task<string> GetAccessToken(string scopes)
{
//var userClaims = User.Identity as System.Security.Claims.ClaimsIdentity;
var userClaims2 = ClaimsPrincipal.Current.Claims;
IConfidentialClientApplication cc = MsalAppBuilder.BuildConfidentialClientApplication();
var userAccount = await cc.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
var userAccount2 = await cc.GetAccountsAsync(ClaimsPrincipal.Current.FindFirst(Globals.ObjectIdClaimType).Value);
var userAccount3 = userAccount2.FirstOrDefault();
AuthenticationResult result = await cc.AcquireTokenSilent(new string[] { scopes }, userAccount3).ExecuteAsync();
return result.AccessToken;
}
My startup.Auth file looks like this:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Sets the ClientId, authority, RedirectUri as obtained from web.config
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, ConfigHelper.Tenant),
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
RedirectUri = ConfigHelper.PostLogoutRedirectUri,
ResponseType = OpenIdConnectResponseTypes.CodeIdToken,
// ValidateIssuer set to false to allow work accounts from any organization to sign in to your application
// To only allow users from a single organizations, set ValidateIssuer to true and 'tenant' setting in web.config to the tenant name or Id (example: contoso.onmicrosoft.com)
// To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = (context) =>
{
//string redirectURI = string.Format("{0}://{1}{2}/", context.Request.Scheme, context.Request.Host, context.Request.PathBase);
string redirectURI = string.Format("https://{0}{1}/", context.Request.Host, context.Request.PathBase);
context.ProtocolMessage.RedirectUri = redirectURI;
context.ProtocolMessage.DomainHint = domain_hint;
return Task.FromResult(0);
},
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = (context) =>
{
if (ConfigHelper.NonceExceptionHandler && (context.Exception.Message.StartsWith("OICE_20004") || context.Exception.Message.Contains("IDX10311") || context.Exception.Message.Contains("IDX21323")))
{
context.SkipToNextMiddleware();
return Task.FromResult(0);
}
return Task.FromResult(0);
}
}
});
app.Use<MsOfficeLinkPrefetchMiddleware>();
// add this function into the app pipeline to Call my function OnAuth and call the next in the pipeline
app.Use((context, next) =>
{
// The function to call...
OnAuth(context);
return next.Invoke();
});
// limit the calls to the above function to be in the PostAuthenticate part of the stage
app.UseStageMarker(PipelineStage.PostAuthenticate);
}
private Task OnAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context)
{
context.HandleResponse();
context.Response.Redirect("/?errormessage=" + context.Exception.Message);
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
string redirectURI = string.Format("{0}://{1}{2}/", notification.Request.Scheme, notification.Request.Host, notification.Request.PathBase);
IConfidentialClientApplication confidentialClient = MsalAppBuilder.BuildConfidentialClientApplication(new ClaimsPrincipal(notification.AuthenticationTicket.Identity));
// Upon successful sign in, get & cache a token using MSAL
//user.readbasic.all
AuthenticationResult result = await confidentialClient.AcquireTokenByAuthorizationCode(new[] { "user.readbasic.all" }, notification.Code).ExecuteAsync();
}
I have been trying for a few days to solve this problem but to no avail. Please Help.
Cheers
I'm trying to implement JWT authentication in a .NET 5 WebAPI. Everything works fine until I add more web servers to the application.
How could I store JWT, for example, in a SQL Server database instead of having them in memory on the single server?
Here's the implementation working on a single server:
Startup
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Context.JwtIssuer,
ValidAudience = Context.JwtIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Context.JwtSecretKey))
};
}
Controller
[HttpGet("Login")]
public IActionResult Login(string uid, string password)
{
var hash = _userManager.GetPasswordHash(uid);
if (string.IsNullOrEmpty(hash))
{
return NotFound("Account not found");
}
if (!_passwordHasher.Check(hash, password))
{
return Unauthorized("Uid/password not correct");
}
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Context.JwtSecretKey));
var token = new JwtSecurityToken(
issuer: Context.JwtIssuer,
audience: Context.JwtIssuer,
expires: DateTime.Now.AddHours(3),
claims: new List<Claim>() { new Claim(ClaimTypes.Name, uid)},
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token),
expiration = token.ValidTo
});
}
How to use refresh_token to obtain a new access token from identity server in Xamarin.Forms client?
I followed tutorial https://sinclairinat0r.com/2018/12/09/secure-data-access-with-identityserver4-and-xamarin-forms and created xamarin forms mobile app with authentication on IS4. I set an access token lifetime to few minutes. After access token expires, as excepted, applciation is no more able to access authorized endpoints. I have an refresh_token but i dont't know how to use it to obtain a new access token from identity server.
Client specified in is4 configuration:
new Client()
{
ClientId = "xamarin-client",
ClientName = "Xamarin client",
AllowedGrantTypes = { "authorization_code" },
AllowedScopes = {"openid", "profile", "values-api" },
AllowAccessTokensViaBrowser = true,
AllowOfflineAccess = true,
AlwaysIncludeUserClaimsInIdToken = true,
RequirePkce = true,
RequireClientSecret = false,
RedirectUris = { "https://iglooidentityserver.azurewebsites.net/grants" },
AccessTokenLifetime = 180,
}
Authenticator i've used:
var oAuth = new OAuth2AuthenticatorEx(
"xamarin-client",
"offline_access values-api",
new Uri("https://iglooidentityserver.azurewebsites.net/connect/authorize"),
new Uri("https://iglooidentityserver.azurewebsites.net/grants"))
{
AccessTokenUrl = new Uri("https://iglooidentityserver.azurewebsites.net/connect/token"),
ShouldEncounterOnPageLoading = false,
};
var presenter = new OAuthLoginPresenter();
presenter.Completed += Presenter_Completed;
presenter.Login(oAuth);
I handled this problem in an old project as follows, hope this helps you.
public async Task<string> GetAccessToken()
{
if ((_authService.AuthAccessTokenExpireIn - DateTime.Now).TotalMinutes < 10) {
var authResponse = await GetRefreshTokenAsync(_authService.AuthRefreshToken);
_authService.AuthAccessToken = authResponse.AccessToken;
_authService.AuthRefreshToken = authResponse.RefreshToken;
_authService.AuthAccessTokenExpireIn = authResponse.ExpiresIn;
}
return _authService.AuthAccessToken;
}
public async Task<UserToken> GetRefreshTokenAsync(string currentRefreshToken)
{
string data = string.Format("grant_type=refresh_token&client_id={0}&client_secret={1}&refresh_token={2}", GlobalSetting.Instance.ClientId, GlobalSetting.Instance.ClientSecret, refreshToken);
var token = await PostAsync<UserToken>(_httpClient,
GlobalSetting.Instance.TokenEndpoint,
data);
return token;
}
public async Task<UserToken> PostAsync<UserToken>(HttpClient httpClient, string uri, object data)
{
var content = new StringContent(JsonConvert.SerializeObject(data));
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
await HandleResponse(response);
string serialized = await response.Content.ReadAsStringAsync();
UserToken result = await Task.Run(() => JsonConvert.DeserializeObject<UserToken>(serialized, _serializerSettings));
return result;
}
I have created an IdentityServer project ( B ) with this configuration :
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
And I have project B with it's custom login page, When the user enter his/her user & password, I send this info to project B's backend, and through this code I can get the token from identity server - project A:
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(_configuration["App:Authority"]);
if (disco.IsError)
{
throw new System.Exception("get info from identity server is failed.");
}
var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "ro.client",
ClientSecret = "secret",
UserName = "bob",
Password = "Pass123$",
Scope = "api1"
});
var accessToken = tokenResponse.AccessToken;
Now what I want to achieve is that somehow I make this user authorized and save the token in cookie, So when this user call another action with authorize attribute, does not redirect him/her to login page.
How can I achieve this ?
I'm trying to implement following functionality:
User signs in into Live Id account from Windows Phone 8.1 (or Universal) app.
App accesses Web Api that I develop with ASP.NET Web Api 2
In this Web Api I need to authenticate the user.
Later, I want to authenticate same user in web app
Here is what I'm doing, and it doesn't work.
In my Windows Phone App:
var authClient = new LiveAuthClient("http://myservice.cloudapp.net");
LiveLoginResult result = await authClient.LoginAsync(new string[] { "wl.signin" });
if (result.Status == LiveConnectSessionStatus.Connected)
{
connected = true;
var identity = await ConnectToApi(result.Session.AuthenticationToken);
Debug.WriteLine(identity);
}
And then
private async Task<string> ConnectToApi(string token)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://myservice.cloudapp.net/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// HTTP GET
HttpResponseMessage response = await client.GetAsync("api/values");
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
return result;
}
else
return response.ReasonPhrase;
}
}
And then in my web api I have following
public void ConfigureAuth(IAppBuilder app)
{
app.UseMicrosoftAccountAuthentication(
clientId: "my client id",
clientSecret: "my secret");
}
I registered http://myservice.cloudapp.net as redirect url.
The problem is authentication doesn't work, web api actions do not recognize the user.
I got it totally wrong. First, I actually need to use app.UseJwtBearerAuthentication method. The example was found here http://code.lawrab.com/2014/01/securing-webapi-with-live-id.html. But when I tried, I got this error in the output
IDX10500: Signature validation failed. Unable to resolve SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
IsReadOnly = False,
Count = 1,
Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
)
This one took me a while to figure out, until I found this post: JwtSecurityTokenHandler 4.0.0 Breaking Changes?
Putting these things together, I got the solution that seems to work now in my testing environment:
public void ConfigureAuth(IAppBuilder app)
{
var sha256 = new SHA256Managed();
var sKey = "<Secret key>" + "JWTSig";
var secretBytes = new UTF8Encoding(true, true).GetBytes(sKey);
var signingKey = sha256.ComputeHash(secretBytes);
var securityKeyProvider = new SymmetricKeyIssuerSecurityTokenProvider("urn:windows:liveid", signingKey);
var securityKey = securityKeyProvider.SecurityTokens.First().SecurityKeys.First();
var jwtOptions = new JwtBearerAuthenticationOptions()
{
//AllowedAudiences = new[] { "<url>" },
//IssuerSecurityTokenProviders = new[]
//{
// new SymmetricKeyIssuerSecurityTokenProvider("urn:windows:liveid",signingKey)
//},
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters()
{
IssuerSigningKeyResolver = (token, securityToken, keyIdentifier, validationParameters) =>
{
return securityKey;
},
ValidAudience = "<url>",
ValidIssuer = securityKeyProvider.Issuer
}
};
app.UseJwtBearerAuthentication(jwtOptions);
}
For anybody looking to do this from JavaScript I managed to get this working by following steps from this blog. You can find the audience by putting your token through jwt.io
https://blog.dirk-eisenberg.de/2014/08/30/validate-authentication_token-from-microsoft-liveid-with-node-express-jwt/
const validateLiveJWT = (token) => {
const secret = '<<SECRET>>';
const sha256 = crypto.createHash('sha256');
sha256.update(secret + 'JWTSig', 'utf8');
const secretBase64 = sha256.digest('base64');
const secret = new Buffer(secretBase64, 'base64');
const options = {
audience: '<<AUDIENCE>>',
issuer: 'urn:windows:liveid',
};
return new Promise((resolve) => {
jwt.verify(token, secret, options, (err: any, claims: any) => {
if (err) {
resolve(undefined);
} else {
resolve(claims);
}
});
});
}