I am working on authorizing a LinkedIn app for users in our web application. In the base its using angularjs with a .NET web api backend on .NET 4.5.2.
I did get it to work until the callback. Here is the code:
public string GetAuthenticationUrl(string context, string redirectUrl)
{
if(context == null)
throw new ArgumentException(nameof(context));
if (string.IsNullOrEmpty(redirectUrl))
throw new ArgumentException(nameof(redirectUrl));
var appContext = _applicationContextProvider.GetLinkedInApplicationContext();
var linkedIn = new UserAgentClient(new AuthorizationServerDescription
{
TokenEndpoint = new Uri(appContext.AccessTokenEndpoint),
AuthorizationEndpoint = new Uri(appContext.AuthorizationEndpoint),
ProtocolVersion = ProtocolVersion.V20
}, appContext.ClientId, appContext.ClientSecret);
var target = linkedIn.RequestUserAuthorization(null, context, new Uri(redirectUrl));
return target.AbsoluteUri;
}
which returns a Url i can use to go to LinkedIn.
I authorize the application and get redirected successfully to my redirectUrl where i get a code and a state.
but what do i do next to request the access token?
public void Confirm(string state, string code)
{
// ?
}
I use dotnetopenauth 4.3
best reagrds
Jonas
i figured it out. So for future adventurers:
public void Confirm(string context, string code)
{
if (context == null)
throw new ArgumentException(nameof(context));
if (string.IsNullOrEmpty(code))
throw new ArgumentException(nameof(code));
var appContext = _applicationContextProvider.GetLinkedInApplicationContext();
var linkedIn = new UserAgentClient(new AuthorizationServerDescription
{
TokenEndpoint = new Uri(appContext.AccessTokenEndpoint),
AuthorizationEndpoint = new Uri(appContext.AuthorizationEndpoint),
ProtocolVersion = ProtocolVersion.V20
}, appContext.ClientId, appContext.ClientSecret)
{
ClientCredentialApplicator = ClientCredentialApplicator.PostParameter(appContext.ClientSecret)
};
var auth = linkedIn.ProcessUserAuthorization(new Uri($"http://fake.url?code={code}"), new AuthorizationState
{
Callback = new Uri("<addd original return_url here from code request>")
});
}
Related
I'm setting up a client credential flow with my identity server to get an access token from a client. I'm able to get the access token with the following code,
Identity server configuration:
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var corsPolicyService = new DefaultCorsPolicyService()
{
AllowAll = true
};
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get());
var options = new IdentityServerOptions
{
Factory = idServerServiceFactory,
SiteName = "Demo",
IssuerUri = IdentityConstants.IssuerUri,
PublicOrigin = IdentityConstants.STSOrigin,
SigningCertificate = LoadCertificate()
};
idsrvApp.UseIdentityServer(options);
});
}
Identity Server - Client configuration:
public static class Clients
{
public static IEnumerable<Client> Get()
{
return new[]
{
new Client
{
ClientId = "ClientSDK",
ClientName = "Client SDK (Client Credentials)",
Flow = Flows.ClientCredentials,
AllowAccessToAllScopes = true,
ClientSecrets = new List<Secret>()
{
new Secret(IdentityConstants.ClientSecret.Sha256())
}
}
};
}
}
MVC Client:
var oAuth2Client = new TokenClient(
IdentityConstants.STSTokenEndpoint,
"ClientSDK",
IdentityConstants.ClientSecret);
var tokenResponse = oAuth2Client.RequestClientCredentialsAsync("MyScope").Result;
return tokenResponse.AccessToken;
I'm able to get the access token(i.e. JWT). Can one please tell me how to add a unique key like (UserId) from my database, when the JWT is created with its claims data when the token is created.
First, you need to create custom attribute "userId" on Azure Portal, and apply it for selected application. Then follow this example,
Update user using Graph API
If you are using built in user flows, then you need to select "userId" for your application.
If you are using custom policy, then following process.
JWT token shows only output claims of Azure AD B2C custom policy. It is a multi steps process to create and update custom policy. Here is link to read more about How to create custom attribute
You should implement custom user store for validating user and adding claims from database. Change startup code like below, Userrepository class represents database communication to authenticate user and get claims from database:
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.AddCustomUserStore();
Add below classes and change according to your requirement:
public static class CustomIdentityServerBuilderExtensions
{
public static IIdentityServerBuilder AddCustomUserStore(this IIdentityServerBuilder builder)
{
builder.AddProfileService<UserProfileService>();
builder.AddResourceOwnerValidator<UserResourceOwnerPasswordValidator>();
return builder;
}
}
public class UserProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
UserRepository userRepository=new UserRepository();
var user = userRepository.GetUserById(int.Parse(context.Subject.GetSubjectId()));
if (user != null)
{
var userTokenModel = _mapper.Map<UserTokenModel>(user);
var claims = new List<Claim>();
claims.Add(new Claim("UserId", user.UserId));
// Add another claims here
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
}
}
public class UserResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
UserRepository userRepository=new UserRepository();
var userLoginStatus = userRepository.GetUserById(context.UserName, context.Password);
if (userLoginStatus != null)
{
context.Result = new GrantValidationResult(userLoginStatus.UserId.ToString(),
OidcConstants.AuthenticationMethods.Password);
}
else
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidClient,
"Wrong Credentials");
}
}
}
I have to implement three-legged authentication in ASP.NET MVC. I have followed the steps according to the Blackboard documentation, especially the link https://community.blackboard.com/docs/DOC-3976-three-legged-oauth
I have received authorization code by calling the REST API /learn/api/public/v1/oauth2/authorizationcode.After that according to the documentation (I followed the documentation exactly but I don't know what am I have been missing ), I built a POST request to /learn/api/public/v1/oauth2/token to get access_token but I am unable to get access_token.
Instead, access_token, I have been receiving a BadRequest. This means I am making a mistake to build my second request but I am unable to fix the problem. I haven't found any code sample in .NET to implement three legged authentication for Blackboard Learn. Could you please help me to resolve the issue?
This is my code to call both APIs to receive access_token.
public class HomeController : Controller
{
public ActionResult Index()
{
// GET /learn/api/public/v1/oauth2/authorizationcode
Guid stateId = Guid.NewGuid();
string applicationKey = "Application key goes here";
string redirectUrl = string.Format("https://Blackboard Learn URL goes here/learn/api/public/v1/oauth2/authorizationcode" +
"?redirect_uri=https://localhost:44300/Home/OAuth2Response&response_type=code&client_id={0}&scope=read&state={1}",
applicationKey, stateId);
Response.Redirect(redirectUrl, true);
return View();
}
public async Task<bool> OAuth2Response(string code = null, string state = null, string error = null, string error_description = null)
{
bool success = true;
string json = string.Empty;
string urlCommand = string.Format("/learn/api/public/v1/oauth2/token?code={0}&redirect_url=https://localhost:44300/Home/OAuth2Response", code);
try
{
using (HttpClient client = new HttpClient())
{
var endpoint = new Uri("Blackboard Learn URL goes here" + urlCommand);
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
HttpContent body = new FormUrlEncodedContent(postData);
// POST /learn/api/public/v1/oauth2/token
using (HttpResponseMessage response = await client.PostAsync(endpoint, body)) // Problem is here
{
if (response.IsSuccessStatusCode)
{
json = await response.Content.ReadAsStringAsync();
}
else
{
success = false;
}
}
}
}
catch (Exception err)
{
//hopefully we never end up here, log this exception for forensics
success = false;
}
return success;
}
}
NOTE: I can successfully receive an access_token in Postman tool.
Finally, the below code works perfectly for 3 legged authentications in ASP.NET MVC.
public class HomeController : Controller
{
//https://blackboard.jiveon.com/docs/DOC-3976-three-legged-oauth
public ActionResult Index()
{
// GET /learn/api/public/v1/oauth2/authorizationcode
Guid stateId = Guid.NewGuid();
string applicationKey = "Application key goes here";
string redirectUrl = string.Format("Blackboard Learn URL goes here/learn/api/public/v1/oauth2/authorizationcode" +
"?redirect_uri=https://localhost:44300/Home/OAuth2Response&response_type=code&client_id={0}&scope=read&state={1}",
applicationKey, stateId);
Response.Redirect(redirectUrl, true);
return View();
}
public async Task<bool> OAuth2Response(string code = null, string state = null, string error = null, string error_description = null)
{
bool success = true;
string json = string.Empty;
string urlCommand = string.Format("/learn/api/public/v1/oauth2/token?code={0}&redirect_uri=https://localhost:44300/Home/OAuth2Response", code);
try
{
using (HttpClient client = new HttpClient())
{
var endpoint = new Uri("Blackboard Learn URL goes here" + urlCommand);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes("client_id:client_secret")));
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("grant_type", "authorization_code"));
HttpContent body = new FormUrlEncodedContent(postData);
using (HttpResponseMessage response = await client.PostAsync(endpoint, body))
{
if (response.IsSuccessStatusCode)
{
json = await response.Content.ReadAsStringAsync();
dynamic oauth2Result = Newtonsoft.Json.JsonConvert.DeserializeObject(json);
string access_token = oauth2Result.access_token;
string refresh_token = oauth2Result.refresh_token; }
else
{
success = false;
}
}
}
}
catch (Exception err) {
//hopefully we never end up here, log this exception for forensics
success = false;
}
return success;
}
}
We are using packages Google.Apis.Calendar.v3 and Google.Apis.Auth.Mvc in an ASP .NET MVC application with users.
We want each user to have access to their Google Calendar, so it is necessary that each one of them give permission to our application. We have read a lot and it is said that there is a way to prevent the application from asking the user for these credentials every hour
We have looked at solutions like this and do not work for us
solutions like this one we do not know how to use them in our MVC application
users may or may not register on our site using google
This is the last code that we tried and everything works correctly, except that every hour the users who had already given permission to our application had to do it again and to re-authenticate with google and give permissions:
FlowMetadata implementation
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow =
new ForceOfflineGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "MY_VALID_CLIENT_ID",
ClientSecret = "MY_VALID_CLIENT_SECRET"
},
Scopes = new[] { CalendarService.Scope.Calendar },
DataStore = new FileDataStore("Google.Api.Auth.Store")
});
public override string GetUserId(Controller controller)
{
var userId = controller.User.Identity.GetUserId();
return userId;
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}
GoogleAuthorizationCodeFlow implementation forcing offline
internal class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public ForceOfflineGoogleAuthorizationCodeFlow(GoogleAuthorizationCodeFlow.Initializer initializer) : base(initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
{
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
AccessType = "offline",
ApprovalPrompt = "force"
};
}
}
AuthCallbackController
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
{
get { return new AppFlowMetadata(); }
}
}
Test controller action
public async Task IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = result.Credential,
ApplicationName = "TEST",
});
IList list = service.CalendarList.List().Execute().Items;
var selected = list.First();
EventsResource.ListRequest request = service.Events.List(selected.Id);
request.TimeMin = new DateTime?(new DateTime(2017, 1, 1));
request.ShowDeleted = false;
request.SingleEvents = true;
request.MaxResults = 10;
request.OrderBy = EventsResource.ListRequest.OrderByEnum.StartTime;
Events events = request.Execute();
return View(events);
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
I created both a MVC 5 web app hosted on Azure and a WPF client. My short term purpose (as if I can achieve that I'll be able to implement all my uses case) is the following:
Enforce Azure Ad authentification on the WPF client
Have the MVC web app to check through Azure Graph API the AD group membership of the user authentified in the client
Send back Graph API object to the client (IUser, Group...)
Use group membership to define Authorization on controllers
My actual issue is the following:
The user launch the app, and is prompted for authentication. I guess it work as I can display the user's mail and I have an access token.
The user tries to access a web api controller and it works fine
The user tries to access another web api controller decorated with [Authorize] and i get in return some HTML page stating this : "We can't sign you in.Your browser is currently set to block JavaScript. You need to allow JavaScript to use this service."
From what I've found searching on the web it seems that it could be related to my web app that is not configured properly (I already tried to add my webapp url in trusted sites and I'm sure that my controller URL is Ok). i cannot find much doc on native client + AAD + MVC so I don't really know how to correct it.
Here's my startup.auth.cs from the webapp :
public partial class Startup
{
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string tenantId = ConfigurationManager.AppSettings["ida:TenantId"];
private static string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
private static string certName = ConfigurationManager.AppSettings["ida:CertName"];
public static readonly string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string graphResourceId = ConfigurationManager.AppSettings["ida:GraphUrl"];
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
//
// If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away.
//
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
#region Certs (not used)
if (certName.Length != 0)
{
// Create a Client Credential Using a Certificate
//
// Initialize the Certificate Credential to be used by ADAL.
// First find the matching certificate in the cert store.
//
X509Certificate2 cert = null;
X509Store store = new X509Store(StoreLocation.CurrentUser);
try
{
store.Open(OpenFlags.ReadOnly);
// Place all certificates in an X509Certificate2Collection object.
X509Certificate2Collection certCollection = store.Certificates;
// Find unexpired certificates.
X509Certificate2Collection currentCerts = certCollection.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
// From the collection of unexpired certificates, find the ones with the correct name.
X509Certificate2Collection signingCert = currentCerts.Find(X509FindType.FindBySubjectDistinguishedName, certName, false);
if (signingCert.Count == 0)
{
// No matching certificate found.
return Task.FromResult(0);
}
// Return the first certificate in the collection, has the right name and is current.
cert = signingCert[0];
}
finally
{
store.Close();
}
// Then create the certificate credential.
ClientAssertionCertificate credential = new ClientAssertionCertificate(clientId, cert);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst(
"http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
AuthenticationHelper.token = result.AccessToken;
}
#endregion
else
{
// Create a Client Credential Using an Application Key
ClientCredential credential = new ClientCredential(clientId, appKey);
string userObjectID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceId);
AuthenticationHelper.token = result.AccessToken;
}
return Task.FromResult(0);
}
}
});
}
}
Here's the controller which can be acceded when not decorated with [Authorize] but in that case the action throw a null exception (but if I can't get it fixed i'll post another question):
[System.Web.Http.Authorize]
public class UserADGraphController : ApiController
{
[ResponseType(typeof(IUser))]
[System.Web.Http.Route("api/UserADGraphController/GetMyInformations")]
public IHttpActionResult GetMyInformations()
{
try
{
string uID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
if (uID == null)
return Ok("UId null");
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
if (client == null)
return Ok("Client null");
IUser adUser = client.Users.Where(u => u.ObjectId == uID).ExecuteAsync().Result.CurrentPage.SingleOrDefault();
if (adUser == null)
{
return NotFound();
}
return Ok(adUser);
}
catch (Exception e)
{
return Ok(e.Message + " " + e.StackTrace);
}
and finally here are relevant parts of the client:
In the mainviewmodel class:
#region Azure AD auth properties
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
Uri redirectUri = new Uri(ConfigurationManager.AppSettings["ida:RedirectUri"]);
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static string AppServiceResourceId = ConfigurationManager.AppSettings["todo:AppServiceResourceId"];
private static string AppServiceBaseAddress = ConfigurationManager.AppSettings["todo:AppServiceBaseAddress"];
private HttpClient httpClient;
private AuthenticationContext authContext = null;
#endregion
In the mainviewmodel constructor:
authContext = new AuthenticationContext(authority);
httpClient = new HttpClient();
My sign in method:
{
AuthenticationResult result = null;
try
{
result = authContext.AcquireToken(AppServiceResourceId, clientId, redirectUri, PromptBehavior.Auto);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
SignInLabelContent = "Connected to azure AD as " + result.UserInfo.DisplayableId;
}
catch (AdalException ex)
{
if (ex.ErrorCode == "user_interaction_required")
{
}
else
{
// An unexpected error occurred.
string message = ex.Message;
if (ex.InnerException != null)
{
message += "Inner Exception : " + ex.InnerException.Message;
}
Messenger.Default.Send<NotificationMessage>(new NotificationMessage(message));
//MessageBox.Show(message);
}
return;
}
}
The method that access the protected controller:
IUser me = null;
AuthenticationResult result = null;
result = authContext.AcquireToken(AppServiceResourceId, clientId, redirectUri, PromptBehavior.Auto);
string authHeader = result.CreateAuthorizationHeader();
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
//HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AppServiceBaseAddress + "/api/UserADGraphController/GetMyInformations");
//request.Headers.TryAddWithoutValidation("Authorization", authHeader);
//HttpResponseMessage response = await client.SendAsync(request);
//string responseString = await response.Content.ReadAsStringAsync();
//LogManager.log(responseString);
//Messenger.Default.Send<NotificationMessage>(new NotificationMessage(responseString));
HttpResponseMessage response = await httpClient.GetAsync(AppServiceBaseAddress + "/api/UserADGraphController/GetMyInformations");
if (response.IsSuccessStatusCode)
{
var jsonString = await response.Content.ReadAsStringAsync();
LogManager.log(jsonString);
me = JsonConvert.DeserializeObject<IUser>(jsonString);
//Messenger.Default.Send<NotificationMessage>(new NotificationMessage(jsonString));
}
In my case response has status code 200 but the jsonString contains the web page telling me about javascript disabled.
If someone has an idea it would be great !
Thanks !
If someone gets into this issue, I managed to solve it by changing my configureAuth method this way :
var azureADBearerAuthOptions = new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = tenant
};
azureADBearerAuthOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = audience
};
app.UseWindowsAzureActiveDirectoryBearerAuthentication(azureADBearerAuthOptions);
This error message is very misleading. I was getting the same problem, and found that my issue was actually mismatched Client Secret/AppURI settings.
From the error message, I assumed it was something I was doing in the ConfigureAuth method. Turns out I was mixing up dev and test settings.
Maybe this will help others who end up on this confusing error message.
I have followed the tutorials up till this point in the series. I am using one project in the solution that acts as both the token issuing authority as well as the resource server.
The JWT is generated using the endpoint mentioned in the startup class and I validated it on jwt.io as well. However when I pass this JWT using Postman on Chrome to the resource API end point secured with an Authorize attribute, I always find it returning
{
"message": "Authorization has been denied for this request." }
The other api method as in the api controller class below works when called thru Postman on Chrome.
I have used the latest versions of all dlls required from the nuget console
Code in the startup class
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
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 CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat(ConfigurationManager.AppSettings["Issuer"]),
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app)
{
string issuer = ConfigurationManager.AppSettings["Issuer"];
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["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)
}
});
}
Code in the Custom OAuthProvider
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override Task MatchEndpoint(OAuthMatchEndpointContext context)
{
//avoid pre-flight calls
if (context.OwinContext.Request.Method == "OPTIONS" && context.IsTokenEndpoint)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST" });
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "accept", "authorization", "content-type" });
context.OwinContext.Response.StatusCode = 200;
context.RequestCompleted();
return Task.FromResult<object>(null);
}
return base.MatchEndpoint(context);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
//setting up claims in the constructor of class UserDetails
UserDetails user = new UserDetails();
user.UserName = context.UserName;
user.FirstName = "Dummy First";
user.LastName = "Dummy Last";
ClaimsIdentity identity = new ClaimsIdentity("JWT-BearerAuth-Test");
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
foreach (string claim in user.Claims)
{
identity.AddClaim(new Claim(ClaimTypes.Role, claim));
}
var ticket = new AuthenticationTicket(identity, null);
context.Validated(ticket);
}
}
The custom JWT class
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
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, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
}
The Resource server's Api controller
public class AdminController : ApiController
{
//This call works
public IHttpActionResult ReadData(string id)
{
return Ok("ID sent in:" + id);
}
//[Authorize(Roles="EditRecord")] //doesnt work
[Authorize] //doesnt work either
public IHttpActionResult EditData(string id)
{
return Ok("Edited ID:" + id);
}
}
My environment is VS2013 with Framework 4.5 using OAuth2 with Web Api 2. Please excuse the long post.
You need to make sure that values for issuer, audienceId, and audienceSecret used in method "ConfigureOAuthTokenConsumption" are the same values used when you generated the JWT token, take care of trailing slashes "/".
This is the only thing comes to my mind right now.