private async Task <bool> ValidateAuthorizationHeader(AuthenticationHeaderValue authHeader, string targetUrl, string userId) { // Validate that we have a bearer token if (authHeader == null || !string.Equals(authHeader.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(authHeader.Parameter)) { return(false); } // Validate the token ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(authHeader.Parameter, targetUrl); if (!result.ValidationSucceeded) { return(false); } // Token is valid, now check the sender and action performer // Both should equal the user if (!string.Equals(result.ActionPerformer, userId, StringComparison.OrdinalIgnoreCase) || !string.Equals(result.Sender, string.IsNullOrEmpty(amSender) ? userId : amSender, StringComparison.OrdinalIgnoreCase)) { return(false); } return(true); }
public async Task TestInvalidTokens() { ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync("abc", "https://www.microsoft.com"); Assert.False(result.ValidationSucceeded); }
/// <summary> /// The POST method for the expense controller. /// </summary> /// <param name="value">Value from the POST request body.</param> /// <returns>The asynchronous task.</returns> // POST api/expense public async Task <HttpResponseMessage> Post([FromBody] string value) { HttpRequestMessage request = this.ActionContext.Request; // Validate that we have a bearer token. if (request.Headers.Authorization == null || !string.Equals(request.Headers.Authorization.Scheme, BearerTokenType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(request.Headers.Authorization.Parameter)) { return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError())); } string bearerToken = request.Headers.Authorization.Parameter; ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); // ValidateTokenAsync will verify the following // 1. The token is issued by Microsoft and its digital signature is valid. // 2. The token has not expired. // 3. The audience claim matches the service domain URL. // // Replace https://api.contoso.com with your service domain URL. // For example, if the service URL is https://api.xyz.com/finance/expense?id=1234, // then replace https://api.contoso.com with https://api.xyz.com ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, "https://api.contoso.com"); if (!result.ValidationSucceeded) { if (result.Exception != null) { Trace.TraceError(result.Exception.ToString()); } return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError())); } // We have a valid token. We will verify the sender and the action performer. // You should replace the code below with your own validation logic. // In this example, we verify that the email is sent by [email protected] // and the action performer has to be someone with @contoso.com email. // // You should also return the CARD-ACTION-STATUS header in the response. // The value of the header will be displayed to the user. if (!string.Equals(result.Sender, @"*****@*****.**", StringComparison.OrdinalIgnoreCase) || !result.ActionPerformer.ToLower().EndsWith("@contoso.com")) { HttpResponseMessage errorResponse = request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError()); errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed."); return(errorResponse); } // Further business logic code here to process the expense report. HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK); response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved."); return(response); }
/// <summary> /// The POST method for the ticket controller. /// </summary> /// <param name="cardResponse">Value from the POST request body.</param> /// <returns>The asynchronous task.</returns> // POST api/ticket public async Task <HttpResponseMessage> Post(CardResponse cardResponse) { HttpRequestMessage request = this.ActionContext.Request; // Validate that we have a bearer token. if (request.Headers.Authorization == null || !string.Equals(request.Headers.Authorization.Scheme, BearerTokenType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(request.Headers.Authorization.Parameter)) { return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError())); } string bearerToken = request.Headers.Authorization.Parameter; ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); // ValidateTokenAsync will verify the following // 1. The token is issued by Microsoft and its digital signature is valid. // 2. The token has not expired. // 3. The audience claim matches the service domain URL. ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, WebServiceHost); if (!result.ValidationSucceeded) { if (result.Exception != null) { Trace.TraceError(result.Exception.ToString()); } return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError())); } // We have a valid token. Your application should verify the sender and/or the ActionPerformer // // You should also return the CARD-ACTION-STATUS header in the response. // The value of the header will be displayed to the user. if (!result.Sender.ToLowerInvariant().EndsWith(SenderEmailDomain.ToLowerInvariant())) { HttpResponseMessage errorResponse = request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError()); errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed."); return(errorResponse); } // prepare the response HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK); response.Headers.Add("CARD-ACTION-STATUS", "Comment recorded..."); #region Business logic code here to process the support ticket. #endregion return(response); }
public async Task TestValidToken() { var testCert = GetTestCert(); var openIdConfig = GetTestO365OpenIdConnectConfiguration(testCert); CancellationToken cancelToken; Mock <IConfigurationManager <OpenIdConnectConfiguration> > mockConfigManager = new Mock <IConfigurationManager <OpenIdConnectConfiguration> >(); mockConfigManager.Setup(cm => cm.GetConfigurationAsync(cancelToken)).ReturnsAsync(openIdConfig); ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(mockConfigManager.Object); string token = CertificateHelper.GenerateJsonWebToken(testCert, "*****@*****.**", "*****@*****.**", "https://api.contoso.com"); ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(token, "https://api.contoso.com"); Assert.True(result.ValidationSucceeded); Assert.Equal("*****@*****.**", result.ActionPerformer); Assert.Equal("*****@*****.**", result.Sender); }
public async Task <IValueProvider> BindAsync(BindingContext context) { var validator = new ActionableMessageTokenValidator(); ActionableMessageTokenValidationResult result = null; if (context.BindingData.TryGetValue("$request", out var request)) { var req = (HttpRequest)request; if (req.Headers.TryGetValue("Authorization", out var authHeader)) { var token = authHeader.FirstOrDefault()?.Substring("Bearer ".Length)?.Trim(); if (!string.IsNullOrEmpty(token)) { var serviceUrl = $"{req.Scheme}://{req.Host}"; result = await validator.ValidateTokenAsync(token, serviceUrl); } } } return(await BindAsync(result, context.ValueContext)); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest request, ILogger log) { log.LogInformation($"Excuting {nameof(AuthorizationTest)}."); var bearerToken = request.Headers["Authorization"].ToString(); log.LogDebug($"Found bearer token `{bearerToken}`"); var baseUrl = $"{request.Scheme}{Uri.SchemeDelimiter}{request.Host.Value}"; var validationResult = new ActionableMessageTokenValidationResult(); try { var tokenValidator = new ActionableMessageTokenValidator(); validationResult = await tokenValidator.ValidateTokenAsync(bearerToken.Replace("Bearer ", ""), baseUrl); } catch (Exception ex) { log.LogError(ex, "Validation failed"); } if (validationResult.ValidationSucceeded) { log.LogInformation($"Token is valid! With sender `{validationResult.Sender}` and performer `{validationResult.ActionPerformer}`."); } else { log.LogWarning($"{validationResult.Exception}"); } log.LogInformation($"Excuted {nameof(AuthorizationTest)}."); return(new OkResult()); }
public static async Task <string> ValidateAuthorizationHeader(AuthenticationHeaderValue authHeader) { // Validate that we have a bearer token if (authHeader == null || !string.Equals(authHeader.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(authHeader.Parameter)) { return(string.Empty); } // Validate the token ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(authHeader.Parameter, merchantId); if (!result.ValidationSucceeded) { return(string.Empty); } // Token is valid, return the action performer, which is the email // address of the user that took the action. This should match // the email that you sent the invoice to return(result.ActionPerformer); }
public static async Task <ValidationModel> ValidateTokenAsync(HttpRequestMessage request) { var sender = ConfigurationManager.AppSettings["sender"].ToLower(); var emailDomain = sender.Substring(sender.IndexOf("@") + 1); var registeredActionURL = ConfigurationManager.AppSettings["registeredActionURL"].ToLower(); var message = string.Empty; // Validate that we have a bearer token. if (request.Headers.Authorization == null || !string.Equals(request.Headers.Authorization.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(request.Headers.Authorization.Parameter)) { message = "Missing authentication token."; return(new ValidationModel { IsError = true, Message = message, Response = CreateCardResponse(request, HttpStatusCode.Unauthorized, message) }); } //Validate the token var validator = new ActionableMessageTokenValidator(); var result = await validator.ValidateTokenAsync(request.Headers.Authorization.Parameter, registeredActionURL); if (!result.ValidationSucceeded) { message = "Invalid token."; return(new ValidationModel { IsError = true, Message = message, Response = CreateCardResponse(request, HttpStatusCode.Unauthorized, message), ValidationResult = result }); } //The sender is registered in the portal and should be a static email address. if (result.Sender.ToLower().CompareTo(sender) != 0) { message = "Invalid sender."; return(new ValidationModel { IsError = true, Message = message, Response = CreateCardResponse(request, HttpStatusCode.Forbidden, message), ValidationResult = result }); } //TODO: Add additional logic to validate the performer. // Here we just compare against the domain. if (!result.ActionPerformer.ToLower().EndsWith(emailDomain)) { message = "The performer is not allowed."; return(new ValidationModel { IsError = true, Message = message, Response = CreateCardResponse(request, HttpStatusCode.Forbidden, message), ValidationResult = result }); } //Return a validation model without creating a response, caller must create their own response. return(new ValidationModel { IsError = false, ValidationResult = result }); }
/// <summary> /// The POST method for the ticket controller. /// </summary> /// <param name="cardResponse">Value from the POST request body.</param> /// <returns>The asynchronous task.</returns> // POST api/ticket public async Task <HttpResponseMessage> Post(Models.CardResponse cardResponse) { HttpRequestMessage request = this.ActionContext.Request; // Validate that we have a bearer token. if (request.Headers.Authorization == null || !string.Equals(request.Headers.Authorization.Scheme, BearerTokenType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(request.Headers.Authorization.Parameter)) { return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError())); } string bearerToken = request.Headers.Authorization.Parameter; ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); // ValidateTokenAsync will verify the following // 1. The token is issued by Microsoft and its digital signature is valid. // 2. The token has not expired. // 3. The audience claim matches the service domain URL. ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, WebServiceHost); if (!result.ValidationSucceeded) { if (result.Exception != null) { Trace.TraceError(result.Exception.ToString()); } return(request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError())); } // We have a valid token. Your application should verify the sender and/or the ActionPerformer // // You should also return the CARD-ACTION-STATUS header in the response. // The value of the header will be displayed to the user. if (!result.Sender.ToLower().EndsWith(SenderEmailDomain)) { HttpResponseMessage errorResponse = request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError()); errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed."); return(errorResponse); } // prepare the response HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK); response.Headers.Add("CARD-ACTION-STATUS", "Comment recorded..."); // Further business logic code here to process the support ticket. #region Business logic code here to process the support ticket. List <Models.Comment> comments = new List <Models.Comment>(); string newComment = cardResponse.Comment; if (cardResponse.CachedComments != null) { JArray cachedComments = (JArray)cardResponse.CachedComments; comments.AddRange(cachedComments.ToObject <List <Models.Comment> >()); } // add this comment comments.Add(new Models.Comment() { ActionPerformer = result.ActionPerformer, CommentDate = DateTime.Now, CommentText = newComment }); // create the card AdaptiveCards.AdaptiveCard refreshCard = CreateRefreshCard(comments); if (refreshCard != null) { // add the Action.Http block to the card. refreshCard.Actions.Add(CreateHttpAction(comments)); response.Headers.Add("CARD-UPDATE-IN-BODY", "true"); response.Content = new StringContent(refreshCard.ToJson()); } #endregion return(response); }
public HttpStatusCode validateSecurity(HttpRequestMessage request, TraceWriter log) { log.Info($"Started Token Validation"); // Validate that we have a bearer token. if (request.Headers.Authorization == null || !string.Equals(request.Headers.Authorization.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(request.Headers.Authorization.Parameter)) { //return request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()); return(HttpStatusCode.Unauthorized); } // Get the token from the Authorization header string bearerToken = request.Headers.Authorization.Parameter; ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); // This will validate that the token has been issued by Microsoft for the // specified target URL i.e. the target matches the intended audience (“aud” claim in token) // // In your code, replace https://api.contoso.com with your service’s base URL. // For example, if the service target URL is https://api.xyz.com/finance/expense?id=1234, // then replace https://api.contoso.com with https://api.xyz.com var task = Task.Run(async() => await validator.ValidateTokenAsync(bearerToken, ServiceBase)); task.Wait(); if (task.Result != null) { ActionableMessageTokenValidationResult result = task.Result; if (!result.ValidationSucceeded) { if (result.Exception != null) { Trace.TraceError(result.Exception.ToString()); } //return request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError()); return(HttpStatusCode.Unauthorized); } // We have a valid token. We will now verify that the sender and action performer are who // we expect. The sender is the identity of the entity that initially sent the Actionable // Message, and the action performer is the identity of the user who actually // took the action (“sub” claim in token). // // You should replace the code below with your own validation logic // In this example, we verify that the email is sent by [email protected] (expected sender) // and the email of the person who performed the action is [email protected] (expected recipient) var _username = Environment.GetEnvironmentVariable("AMUser"); if (!string.Equals(result.Sender, _username, StringComparison.OrdinalIgnoreCase) || !string.Equals(result.ActionPerformer.Split('@')[1], _username.Split('@')[1], StringComparison.OrdinalIgnoreCase)) { return(HttpStatusCode.Forbidden); } return(HttpStatusCode.OK); } return(HttpStatusCode.InternalServerError); }
public async Task TestNullOrEmptyToken(string token) { ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); await Assert.ThrowsAsync <ArgumentException>(async() => await validator.ValidateTokenAsync(token, null)); }
public async Task <IActionResult> Post([FromBody] dynamic value, [FromHeader] string authorization) { var tunnel = await _ngrok.GetTunnelsAsync(); var actionUrl = tunnel.Where(i => i.Proto == "https").Select(i => i.PublicUrl).FirstOrDefault(); // Authentication if (!AuthenticationHeaderValue.TryParse(authorization, out var headerValue)) { return(Unauthorized("Please authenticate")); } var scheme = headerValue.Scheme; var parameter = headerValue.Parameter; if (!string.Equals(scheme, "bearer", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(parameter)) { return(Unauthorized("Incorrect authentication scheme")); } var validator = new ActionableMessageTokenValidator(); ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(parameter, actionUrl); if (!result.ValidationSucceeded) { if (result.Exception != null) { _logger.LogError(result.Exception.ToString()); } return(Unauthorized("Invalid authorization code")); } var allowListDomain = new List <string> { "onmicrosoft.com", "libinuko.com" }; if (allowListDomain.Where(i => result.Sender.EndsWith(i, StringComparison.InvariantCultureIgnoreCase)).Count() <= 0) { Response.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed."); return(Forbid("Invalid sender")); } // Process valid request JObject jObject = JObject.Parse(value.ToString()); var feedbackResponse = jObject.ToObject <FeedbackModel>(); // Fake feedback string fakeFeedbackPath = Path.Combine(_env.ContentRootPath, "assets", "fake-feedback.json"); var fakeFeedback = JsonSerializer.Deserialize <List <FeedbackItem> >( await System.IO.File.ReadAllTextAsync(fakeFeedbackPath, Encoding.UTF8) ); fakeFeedback.Add(new FeedbackItem { name = result.Sender, comment = feedbackResponse.Comment, rating = feedbackResponse.Rating }); await System.IO.File.WriteAllTextAsync(fakeFeedbackPath, JsonSerializer.Serialize(fakeFeedback)); var feedbackSummary = new { average_rating = fakeFeedback.Average(i => i.rating), feedback = fakeFeedback, total_responses = fakeFeedback.Count() }; // Response string cardJsonPath = Path.Combine(_env.ContentRootPath, "assets", "response-card.json"); string cardJson = await System.IO.File.ReadAllTextAsync(cardJsonPath, Encoding.UTF8); var cardTemplate = new AdaptiveCardTemplate(cardJson); var card = cardTemplate.Expand(feedbackSummary); Response.Headers.Add("CARD-ACTION-STATUS", "The M365 Developer Bootcamp was received."); Response.Headers.Add("CARD-UPDATE-IN-BODY", "true"); return(Ok(card)); }
/// <inheritdoc /> public async Task <SurveyResponse> PostSurveyAsync(SurveyRequest request) { WebOperationContext context = WebOperationContext.Current; SurveyResponse response = new Models.SurveyResponse(); // Validate that we have a bearer token. string authorization = context.IncomingRequest.Headers["authorization"]; if (string.IsNullOrEmpty(authorization)) { response.IsError = true; response.Message = "Bearer token not found."; context.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; return(response); } string[] parts = authorization.Split(' '); if (parts.Length != 2 || !string.Equals(parts[0], BearerTokenType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(parts[1])) { response.IsError = true; response.Message = "Bearer token not found."; context.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; return(response); } string bearerToken = parts[1]; ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); // ValidateTokenAsync will verify the following // 1. The token is issued by Microsoft and its digital signature is valid. // 2. The token has not expired. // 3. The audience claim matches the service domain URL. // // Replace https://api.contoso.com with your service domain URL. // For example, if the service URL is https://api.xyz.com/finance/expense?id=1234, // then replace https://api.contoso.com with https://api.xyz.com. ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, "https://api.contoso.com"); if (!result.ValidationSucceeded) { if (result.Exception != null) { Trace.TraceError(result.Exception.ToString()); response.Message = result.Exception.Message; } response.IsError = true; context.OutgoingResponse.StatusCode = HttpStatusCode.Unauthorized; return(response); } // We have a valid token. We will verify the sender and the action performer. // You should replace the code below with your own validation logic. // In this example, we verify that the email is sent by [email protected] // and the action performer has to be someone with @contoso.com email. // // You should also return the CARD-ACTION-STATUS header in the response. // The value of the header will be displayed to the user. if (!string.Equals(result.Sender, @"*****@*****.**", StringComparison.OrdinalIgnoreCase) || !result.ActionPerformer.EndsWith("@contoso.com")) { response.IsError = true; response.Message = "Invalid sender or the action performer is not allowed."; context.OutgoingResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed."); context.OutgoingResponse.StatusCode = HttpStatusCode.Forbidden; return(response); } // Further business logic code here to process the expense report. context.OutgoingResponse.Headers.Add("CARD-ACTION-STATUS", "The survey was accepted."); return(response); }