I'm writing an Add-in for Office365/Outlook. The Add-in runs on a web-server that presents information from a third-party system. I need to make sure it only presents information related to the username (or email address) logged in. I've successfully sent and validated the Exchange identity token on my server, using the PHP example code provided by Microsoft:
https://dev.office.com/docs/add-ins/outlook/use-php-to-validate-an-identity-token
My problem is that the identity token does not contain any username or email adress, the closest I get is "msexchuid", but I can't make any sense out of that numeric user identifier in the third-party system.
On the client side the Add-in javascript can get a username and email via "Office.context.mailbox.userProfile", however I don't just want to forward that to my web server as it could be faked.
Is there a way to make the Identity token contain the username/email (that would be great!), or is it possible from my web server's server side PHP script lookup further user details based on the identity token?
The id token is used to intend to integrate with third-party application for SSO. As you mentioned that it only include a unique id of Exchange.
As a workaround, we can get from the callback token via the getCallbackTokenAsync method which include the SMTP address directly. And to validate the callback token, we can verify whether we can get the item info with EWS.
For example, there is an ‘parentItemId’ in the callback token. It is same that retrieve the claims from the callback token as id token since there are is JWT token. You can refer to here for more detail.
Then we can use the code below to get the item information from EWS:
public bool Post([FromBody]EWSRequest request)
{
ExchangeService service = new ExchangeService();
service.Credentials = new OAuthCredentials(request.token);
service.Url = new Uri(request.ewsURL);
//get item id from callback token
var itemId = "";
Item item = Item.Bind(service, itemId);
var subject = item.Subject;
return subject.Length>0;
}
public class EWSRequest
{
public string token;
public string ewsURL;
}
JavarScript :
Office.context.mailbox.getCallbackTokenAsync(getCallbackTokenCallback)
function getCallbackTokenCallback(asyncResult) {
var _token = asyncResult.value;
var _ewsURL = Office.context.mailbox.ewsUrl;
var serviceEndpoint = "https://localhost:44300/API/token/"
var postData={ token: _token, ewsURL: _ewsURL }
$.ajax({
url: serviceEndpoint,
type: "post",
contentType: "application/json",
data: JSON.stringify(postData),
success: function (result) {
var ret = result;
}
})
}
Related
I created an MVC page used JWT authentication. Once a user logged in successfully, the app returns a JWT token to the user that's stored in the request header. But then a problem occurred. Another person also signs in with the same user account and changes the password. So the first logged in session should be terminated because of security issues. The solution I thought is invalidating the JWT token of that user. But I have to define when was the user's password changed. The JWT token doesn't contain the password information so I couldn't request to the backend server to determinate the password was changed every time the user (with old password) request to the server, either. I need some ideas, suggestions.
For this feature you should add new property like SerialNumber as string on Users table
public class User { public string SerialNumber { get; set; } }
when you want to create new token for user add user SerialNumber to Claims like this
new Claim(ClaimTypes.SerialNumber, user.SerialNumber, ClaimValueTypes.String, issuer),
and when changed user password or username or status or every important property you should update serial number. when serial changed on token validator method after first http request will raise error code 401 (that means Unauthorized)
public async Task ValidateAsync(TokenValidatedContext context)
{
var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
{
context.Fail("This is not our issued token. It has no claims.");
return;
}
var serialNumberClaim = claimsIdentity.FindFirst(ClaimTypes.SerialNumber);
if (serialNumberClaim == null)
{
context.Fail("This is not our issued token. It has no serial.");
return;
}
var userIdString = claimsIdentity.FindFirst(ClaimTypes.UserData).Value;
if (!int.TryParse(userIdString, out int userId))
{
context.Fail("This is not our issued token. It has no user-id.");
return;
}
var user = await _signInService.GetUserAsync(userId);
if (user == null)
{
context.Fail("User deleted!");
return;
}
if (user.SerialNumber != serialNumberClaim.Value || !user.Status)
{
context.Fail("This token is expired. Please login again.");
return;
}
}
on JWT Token configuration
OnTokenValidated = context =>
{
var tokenValidatorService = context.HttpContext.RequestServices.GetRequiredService<ITokenFactoryService>();
return tokenValidatorService.ValidateAsync(context);
},
Using the password directly in the token is a security risk, since an attacker could retrieve it from the user's computer. Better to either:
include a unique token ID in the token, and maintain a list of revoked tokens (or of allowed tokens; whichever is a better fit). or
include a "version number" in the user list, change it whenever the password is changed, and include the version number when the token is issued. That way, all old tokens can be rejected. #Mohammad's answer has an example of something similar.
None of those pieces of information means anything by themselves.
Use password as claim in jwt?
It will be checked in every request, so after password is changed it will return 401
I am looking into using MSAL and client credential flow, however, there is one thing I don't fully understand.
In the example provided by Microsoft:
https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/blob/master/daemon-console/Program.cs
The following code is used to get an access token:
var clientCredentials = new ClientCredential(_clientSecret);
var app = new ConfidentialClientApplication(_clientId, _authority, "https://daemon", clientCredentials, null, new TokenCache());
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
AuthenticationResult result = await app.AcquireTokenForClientAsync(scopes);
Whats with the redirectUri in this case?
I have tried different values as the redirectUri and it seems to work either way... but if I add a relative path or null it fails to obtain a token. What is this value supposed to be?
For a console application it makes little sense to listen on an URL, however, the documentation for ConfidentialClientApplication says that it is required.
To request access token with client credential flow , app will send HTTP POST token request to Azure AD's token endpoint with app's credential , AAD will return access token in response , redirect url is not need in this scenario . According to source code , the redirect url is not used also:
private async Task<AuthenticationResult> AcquireTokenForClientCommonAsync(IEnumerable<string> scopes, bool forceRefresh, ApiEvent.ApiIds apiId, bool sendCertificate)
{
Authority authority = Instance.Authority.CreateAuthority(ServiceBundle, Authority, ValidateAuthority);
AuthenticationRequestParameters parameters = CreateRequestParameters(authority, scopes, null,
AppTokenCache);
parameters.IsClientCredentialRequest = true;
parameters.SendCertificate = sendCertificate;
var handler = new ClientCredentialRequest(
ServiceBundle,
parameters,
apiId,
forceRefresh);
return await handler.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
But you should provide a valid url when initializing the ConfidentialClientApplication at this point .
I am currently working on an interaction between Angular JS app and Node.js Server (as API) with an authentication based on JSON Web Token.
But I have a question I can't answer by myself : when you encode the JWT server-side putting a user as payload, how do you proceed to retrieve the user information client-side ?
Here is a small example to understand my question:
I am a basic user, I send my credentials to the API for authenticating. In exchange, I receive a JWT token but I don't have any information about the user since only the server has the secret key that is able to decode the JWT token. So does the server need to send me for example the id of the user so that I could call my api user/id for retrieving information about the user authenticated?
You retrieve the user's info by decoding the token on each request. So in your example after the token is returned to the client, the client makes a request to the server to grab the user's first and last name using the data stored in the encoded token which is sent along with the request back to the server. When making this GET request, you can send the token as a parameter. I'll use a non-cookie stored example. Here's how it goes down:
The user signs in with their password and username
The server encodes a json web token payload that contains the unique identifier (i.e. user_id) of the user that signed in using the secret_key. An example function call may look something like this.
payload = {user_id: 35}
user_token = JWT.encode(payload, "your_secret_key");
Return the user_token to the client and store said token in a hidden html tag or in a localStorage variable. Using Angular, I'd store it in localStorage.
Now that the user is signed_in and the token is client-side, you can submit a GET request that contains the user_token as a parameter. Remember, this user_token payload contains the user_id.
The server gets the parameter and decodes the user_token to get the user_id from the payload.
You query the database with the user_id and return the data (first and last name) as plain json, NOT ENCODED.
It's important to remember the only thing to encode in your example is the unique identifier (user_id). On each request you decode the token which itself is the authentication mechanism.
You have the payload on the client, If your needed data is in the payload you can easily do a Base64 Decode on payload to find it!
To understand this here are steps:
Client send username:user,password:pass to server.
The server starts the authentication business and finds that the user name and password is valid.
The server must return these information back to client. Here is where JWT has some rules. The server must return a token back to client. The token has three parts Header.PayLoad.Signature . Forget about signature right now, which is the part which make some confusion.
The part one is Header. Some thing like:
{"typ":"JWT","alg":"HS256"}
Which will be eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 after Base64 Decode. Please consider this is just a decode, no encryption at all! To see this you can go to https://www.base64decode.org/ and test.
After the header, the server needs to send a payload to user. The server may decide to send below json ( I said decide, because there is no standard requirement here, you can send more or less data as payload, for example, you may also set user privileges for example admin:true, or user first and last name, but keep in mind that the JWT size must be small as it will be send to server on each request)
{"username":"user","id":3,"iat":1465032622,"exp":1465050622}
Again according to JWT, the server needs a Base64 Decode here ( and again no encryption at all). The above json will be eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.
Until now the server created the Header and Payload. Now time to make signature! It is very easy:
var encodedString=base64UrlEncode(header) + "." + base64UrlEncode(payload);
//As our example base64UrlEncode(header) is eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
//and the base64UrlEncode(payload) is eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9
var signature=HMACSHA256(encodedString, 'a secret string which is kept at server');
The signature is made with a secret key which you don't have it at clent!! You don't need it either. All token data is in the payload and can be accessed with decode ( again no decrypt ! ).
This signature is used at the server, when you send token back to server, the server check that signiature is correct to make sure he can trust the token data.
To summarize have a look at below token
//Header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
//PayLoad
eyJ1c2VybmFtZSI6IjEiLCJpZCI6MywiaWF0IjoxNDY1MDMyNjIyLCJleHAiOjE0NjUwNTA2MjJ9.
//Signature
0K8TL1YS0XKnEIfI3lYs-bu2vbWHSNZsVJkN1mXtgWg
Header and payloads are Base64 Decoded and you can encode it on client. But you can not do any thing with signature.
The signature is only used by the server. The client send each request with his token, the server must be sure that the client did not change any part of token payload (for example change userid). This is where the signature string come importance is revealed, the server recheck the signature with it's secret key for every request!
Note:
Do you still wonder why the JWT use encode and decode ?! To make the hole token URL safe !
The strategy in the accepted answer works, but it misses the fact that the client can see the payload of a JWT. It is explained nicely in The Anatomy of a JSON Web Token.
A JWT has 3 parts. The first two, header and payload, are base64 encoded. The client can decode them easily. The payload has claims about the user, the client can use this data (user id, name, roles, token expiration) w/out having to make another request to the server.
The third part of the JWT is the signature. It is a hash of the header, the payload, and a secret that only the server knows. The server will validate the token and user's permissions on every request.
The client never knows the secret, it just has a token that claims to be the given user.
JWT (JSON web token) has become more and more popular in web development. It is
an open standard which allows transmitting data between parties as a JSON object in a secure and compact way. The data transmitting using JWT between parties are digitally signed so that it can be easily verified and trusted.
JWT in ASP.NET Core
The first step is to configure JWT based authentication in our project. we can add custom jwt auth middleware that fire in every request for Authorization.
Startup.cs
services.AddMvc(options => options.EnableEndpointRouting = false);
var tokenValidationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("Jwt_Key"),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
RequireExpirationTime = false,
ValidIssuer = "Jwt_Issuer",
ValidAudience = "Jwt_Audience",
ClockSkew = TimeSpan.Zero
};
services.AddSingleton(tokenValidationParams);
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwt => {
jwt.SaveToken = true;
jwt.TokenValidationParameters = tokenValidationParams;
});
services.AddMvc();
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// custom jwt auth middleware
**app.UseMiddleware<JwtMiddleware>();**
app.UseAuthentication();
app.UseMvc();
app.Run(async (context) =>
{
await context.Response.WriteAsync("Welcome to DATA API");
});
}
Generete JWT
GenerateJSONWebToken(User userInfo)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Jwt_Key"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, userInfo.UserID),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken("Jwt_Issuer","Jwt:Audience",
claims,
expires: DateTime.Now.AddHours(24),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
This Method return JWT Totken like
Token : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKa
WduZXNoIFRyaXZlZGkiLCJlbWFpbCI6InRlc3QuYnRlc3RAZ21haWwuY29tIiwiRG
F0ZU9mSm9pbmciOiIwMDAxLTAxLTAxIiwianRpIjoiYzJkNTZjNzQtZTc3Yy00ZmU
xLTgyYzAtMzlhYjhmNzFmYzUzIiwiZXhwIjoxNTMyMzU2NjY5LCJpc3MiOiJUZXN0
LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.8hwQ3H9V8mdNYrFZSjbCpWSyR1CNyDYHc
Gf6GqqCGnY"
Calling Authorize Method
[Authorize]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2", "value3", "value4",
"value5" };
}
Validate Token in Jwt Middleware Class
JwtMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenValidationParameters _tokenValidationParams;
public JwtMiddleware(RequestDelegate next, TokenValidationParameters
tokenValidationParams)
{
_next = next;
_tokenValidationParams = tokenValidationParams;
}
public async Task Invoke(HttpContext context)
{
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
var jwtTokenHandler = new JwtSecurityTokenHandler();
// Validation 1 - Validation JWT token format
var tokenInVerification = jwtTokenHandler.ValidateToken(token, _tokenValidationParams, out var validatedToken);
if (validatedToken is JwtSecurityToken jwtSecurityToken)
{
var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase);
if (result == false)
{
Error Invalid = new Error()
{
Success = false,
Errors = "Token is Invalid"
};
context.Items["Error"] = Invalid;
}
}
await _next(context);
}
}
Authorize Attribute
public void OnAuthorization(AuthorizationFilterContext context)
{
var Error= (UserModel)context.HttpContext.Items["Error"];
if (AuthResult != null)
{
// not logged in
context.Result = new JsonResult(new { message = "Unauthorized Access" }) {
StatusCode = StatusCodes.Status401Unauthorized };
}
}
I hope this will work for you.
I am developing a web site using the following technologies:
MVC 4
EF 5
Web Api
Future - possible Windows Phone/Windows 8 application.
I am using Web API so that I have a developed api that I can use on other clients.
However, I will need to authorise the user each time a request is made to the API. My initial thought was to do this via the HTTP headers. However, I'm just wondering if I should just use MVC Controllers instead of Web API for the MVC application and create a RESTful api if I was to develop a phone/win 8 application, again the user would need to be authenticated. So the originally problem still exists.
What are people's thoughts? Can any one point me to a tutorial on how I could securely pass the authenticated users details over the HTTP Header, also something that's a step by step tutorial as I'm going into this from scratch and need to understand it.
I use basic authentication to pass the credentials for authorization. This puts the credentials in the header. To do this is pretty straight forward by using the beforeSend event handler of the JQuery ajax function. Here is an example of how to do this.
getAuthorizationHeader = function (username, password) {
var authType;
var up = $.base64.encode(username + ":" + password);
authType = "Basic " + up;
};
return authType;
};
$.ajax({
url: _url,
data: _data,
type: _type,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", getAuthorizationHeader(username, password));
},
success: ajaxSuccessHandler,
error: ajaxErrHandler
});
This encodes the username/password that is sent in the header. Note that this is not enough security to rely on just the encoding as it is easy to decode. You still want to use HTTPS/SSL to make sure the information sent over the wire is secure.
On the Web API side you can make a custom AuthorizeAttribute that gets the credentials from the header, decodes them, and performs your authorization process. There is a separate AuthorizeAttribute used by the Web API as opposed to the controller. Be sure to use System.Web.Http.AuthorizeAttribute as your base class when creating your custom AuthorizeAttribute. They have different behaviors. The one for the controller will want to redirect to the logon page whereas the one for the Web API returns an HTTP code indicating success or failure. I return an HTTP code of Forbidden if authorization fails to distinguish a failure due to authorization as opposed to authentication so the client can react accordingly.
Here is an example method for getting the credentials from the header that can be used in the custom AuthorizeAttribute.
private bool GetUserNameAndPassword(HttpActionContext actionContext, out string username, out string password)
{
bool gotIt = false;
username = string.Empty;
password = string.Empty;
IEnumerable<string> headerVals;
if (actionContext.Request.Headers.TryGetValues("Authorization", out headerVals))
{
try
{
string authHeader = headerVals.FirstOrDefault();
char[] delims = { ' ' };
string[] authHeaderTokens = authHeader.Split(new char[] { ' ' });
if (authHeaderTokens[0].Contains("Basic"))
{
string decodedStr = SecurityHelper.DecodeFrom64(authHeaderTokens[1]);
string[] unpw = decodedStr.Split(new char[] { ':' });
username = unpw[0];
password = unpw[1];
}
gotIt = true;
}
catch { gotIt = false; }
}
return gotIt;
}
And here is the code for decoding the header data that is used in this method.
public static string DecodeFrom64(string encodedData)
{
byte[] encodedDataAsBytes
= System.Convert.FromBase64String(encodedData);
string returnValue =
System.Text.Encoding.ASCII.GetString(encodedDataAsBytes);
return returnValue;
}
Once you have the username and password you can perform your authorization process and return the appropriate HTTP code to the client for handling.
Updated 3/8/2013
I wrote a blog post that goes into more details on how to implement this with SimpleMembership, the default membership provider for MVC 4 Internet Applications. It also includes a downloadable VS 2012 project that implements this.
Can anyone point me to a suitable WCF Extension Point for hooking into the WCF Pipeline to extract credentials for UserNamePasswordValidator from the headers of an incoming HTTP REST Request?
Yes I know about all the funky stunts with Http Handlers etc. you can pull to somehow get Basic/Digest Auth working but since the client I'm working on will be strictly Javascript based I've opted for a simple model where the credentials are passed using two custom headers over an SSL pipe.
Update: I've managed to improve on this by using the approach described here. While this does not solves the problem described in my question, it gets rid of having to authenticate in a authorization policy since authentication is now handled by a custom AuthenticationManager, bypassing the UsernamePasswordValidator alltogether.
For the time being I've solved the problem by combining Authentication and Authorization in a custom Authorization Policy. I'd still rather find a way to hook into the normal UserNamePasswordValidator authentication scheme because an Authorization Policy is supposed to to Authorization not Authentication.
internal class RESTAuthorizationPolicy : IAuthorizationPolicy
{
public RESTAuthorizationPolicy()
{
Id = Guid.NewGuid().ToString();
Issuer = ClaimSet.System;
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
const String HttpRequestKey = "httpRequest";
const String UsernameHeaderKey = "x-ms-credentials-username";
const String PasswordHeaderKey = "x-ms-credentials-password";
const String IdentitiesKey = "Identities";
const String PrincipalKey = "Principal";
// Check if the properties of the context has the identities list
if (evaluationContext.Properties.Count > 0 ||
evaluationContext.Properties.ContainsKey(IdentitiesKey) ||
!OperationContext.Current.IncomingMessageProperties.ContainsKey(HttpRequestKey))
return false;
// get http request
var httpRequest = (HttpRequestMessageProperty)OperationContext.Current.IncomingMessageProperties[HttpRequestKey];
// extract credentials
var username = httpRequest.Headers[UsernameHeaderKey];
var password = httpRequest.Headers[PasswordHeaderKey];
// verify credentials complete
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
// Get or create the identities list
if (!evaluationContext.Properties.ContainsKey(IdentitiesKey))
evaluationContext.Properties[IdentitiesKey] = new List<IIdentity>();
var identities = (List<IIdentity>) evaluationContext.Properties[IdentitiesKey];
// lookup user
using (var con = ServiceLocator.Current.GetInstance<IDbConnection>())
{
using (var userDao = ServiceLocator.Current.GetDao<IUserDao>(con))
{
var user = userDao.GetUserByUsernamePassword(username, password);
...