I have a desktop app where I authenticate users via Azure via AuthenticationContext.AcquireTokenAsync.
With the result from this method I can get the access-token, send it to my WCF and in my WCF use JwtSecurityToken / ConfigurationManager< OpenIdConnectConfiguration > to validate the token.
I've implemented login via Azure in a web app now by configuring it with app.UseOpenIdConnectAuthentication. So in my web app I dont explicitly call a method that returns a token. Rather I jack this in in asp.net's flow.
But now I want to fetch the token in a method and send it for validation similiarly how I did in my desktop app. I cannot find any token that the ConfigurationManager accepts however. I've looked in the regular HttpContext and Owincontext but no info that I find there is useful. Is the accesstoken stored anywhere where I can fetch it? Or do I have to do another request to get an accesstoken?
You should be getting access token as part of the response.
A simple way would be to look at the Authorization header. Look at code below -
HttpContext.Current.Request.Headers["Authorization"];
Also, I don't know what you mean by send the token for validation.
If you're trying to validate the token manually, here's a sample that does exactly that -
Manually validating a JWT access token in a web API
In the sample, specifically look at the Global.asax.cs
string jwtToken = null;
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader != null)
{
jwtToken = authHeader.Parameter;
}
if (jwtToken == null)
{
HttpResponseMessage response = this.BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
return response;
}
.........
.........
.........
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
// We accept both the App Id URI and the AppId of this service application
ValidAudiences = new[] { audience, clientId },
// Supports both the Azure AD V1 and V2 endpoint
ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
IssuerSigningKeys = signingKeys
};
try
{
// Validate token.
SecurityToken validatedToken = new JwtSecurityToken();
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);
Related
I have a custom implementation of .AddOAuth() in .Net-Core. I've created a nuget package for Authentication using Coinbase (which is basically a clone of the add google implementation plus a few custom options specific to coinbase) full source. I've looked at a few other questions on this however they don't seem to implement OAuth (e.g I cannot pass scopes) I would like to login using OAuth But I want to return to my clients a JWT.
When I try to use JWT with AddCoinbase ( which is just a derrivative of AddOAuth)
services.AddAuthentication(JWT_BEARER_AUTH)
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = Configuration["Tokens:Issuer"],
ValidAudience = Configuration["Tokens:Issuer"],
//TODO: get key from secret section
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Tokens:Key"]))
};
})
.AddCoinbase(options => {
options.AccessAllAccounts = true;
options.SendLimitAmount = 1;
options.SendLimitCurrency = "USD";
options.SendLimitPeriod = SendLimitPeriod.day;
options.ClientId = Configuration["Coinbase:ClientId"];
options.ClientSecret = Configuration["Coinbase:ClientSecret"];
COINBASE_SCOPES.ForEach(scope => options.Scope.Add(scope));
options.SaveTokens = true;
options.ClaimActions.MapJsonKey("urn:coinbase:avatar", "avatar_url");
});
After I login to coinbase the external callback redirects me
[HttpGet("ExternalLoginCallback")]
[AllowAnonymous]
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
//TODO: Handle remote error failure
throw new Exception($"Error from external provider: {remoteError}");
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
//TODO: Handle null external login info
throw new Exception("Error: could not find user info");
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);1
var user = await (result.Succeeded ?
_userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey)
: this.CreateIdentityUser(info));
await _signInManager.UpdateExternalAuthenticationTokensAsync(info);
_logger.LogInformation("User logged in with {Name} provider.", info.LoginProvider);
return Redirect(returnUrl);
}
After the redirect I never receive a JSON Web Token I always receive a Cookie. How can I leverage OAuth Authentication while serving JWT to my Clients?
OAuth is not a Json Web Token solution. OAuth 2.0 provides authorization and optionally identification (OIDC).
When you authorize via an OAuth 2.0 endpoint, you receive an Access Token and optionally an ID Token. The ID Token is a Signed JWT. The Access Token is an opaque object that is a Signed JWT for some vendor implementations but not all (Google is opaque).
After authorization you receive one or two tokens (access and ID). You can wrap them in your own JWT, sign it and then use the combined JWT any way that you want.
I have a WebApi2 app which servers as api for my app frontend. Now i want to use AD B2C to manage my users - let's say I want to differentiate them by their roles (admin or customer) and for that i created two b2c users groups accordingly. When user logs in i want to display different things for users with different roles (groups).
I'm using this example to setup Startup.Auth.cs in my WebApi2 project:
var tvps = new TokenValidationParameters
{
ValidAudience = clientId,
AuthenticationType = signUpSignInPolicy,
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(aadInstance, tenant, defaultPolicy))),
});
From what I have read b2c doesn't return user's grups in claims for now. Some people suggested I need to call GraphApi after obtaining token to fetch these groups and add them to user's claims:
private static async Task<string> GetGroups(string token, string userId)
{
using (var client = new HttpClient())
{
var requestUrl = $"https://graph.microsoft.com/v1.0/users/{userId}/memberOf?$select=displayName";
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
return responseString;
}
}
This is where I've stuck. How can I inject my code to get token for calling graph? I've messed with OAuthBearerAuthenticationOptions.Provider:
Provider = new OAuthBearerAuthenticationProvider
{
OnValidateIdentity = (context) =>
{
// var token = ??
// var userId = <get from context's claims>
// var groups = GetGroups(token, userId);
// <add to claims>
return Task.CompletedTask;
}
},
...but I don't know how to get to token. Maybe that's wrong from the start and I need another approach?
Customer's token cannot be used to call AADGraph/MSGraph Apis. To get token to call graph apis in an automated way, we need app-only access. We need to configre an app in the tenant, the crendetial of which are used to get a token. That token can then be used to call memberOF Api (or any other api which does or require user information to be there)
Here is the sample and explaination of how to call AAD Graph apis in a B2C dependent service.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-devquickstarts-graph-dotnet
I am following the below GitHub sample for implementing Authentication mechanism across WebApp and WebApi.
https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet
I am using a single App registration for both WebApp and WebApi, get a access token for "https://abc.onmicrosoft.com/App" and pass it on to WebApi. I am attaching the token to the HTTPS headers with the name "Bearer". I have the below in the WebApi Owin Startup class to validate the token for the Audience and Tenant, but does not actually validate the token for these as expected.
A couple of questions:
1. What triggers the below handler to validate the token for the tenant and audience? Is it the [Authorize] attribute on the Controller class?
2. How does it where to find the token to execute the handler?
3. Setting the SaveSigninToken to true saves the token. How can I retrieve the token and also Acquire access token for Graph API from this token?
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = "abc.onmicrosoft.com",
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = "https://abc.onmicrosoft.com/App",
SaveSigninToken = true,
}
});
Please advise. Thanks in advance!
What triggers the below handler to validate the token for the tenant and audience?
The middleware runs in Active mode by default, so it will attempt to find a token in every request. If it finds one, it will attempt to validate it. If it finds that it is valid, a ClaimsPrincipal is created which is accessible in further OWIN middleware and Web API components.
It also downloads the public keys with which it checks the token signature on app startup from Azure AD. You can see this if you use a tool like Fiddler.
How does it where to find the token to execute the handler?
I'm not sure if I am understanding this question, I hope my answer above clarified the process.
Setting the SaveSigninToken to true saves the token. How can I retrieve the token and also Acquire access token for Graph API from this token?
What you are trying to do is call an API using the on-behalf-of flow. You can find an example app here: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof. More specifically this part should be of interest to you: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof/blob/master/TodoListService/Controllers/TodoListController.cs#L133.
ClientCredential clientCred = new ClientCredential(clientId, appKey);
var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as System.IdentityModel.Tokens.BootstrapContext;
string userName = ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
string userAccessToken = bootstrapContext.Token;
UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(authority, new DbTokenCache(userId));
// In the case of a transient error, retry once after 1 second, then abandon.
// Retrying is optional. It may be better, for your application, to return an error immediately to the user and have the user initiate the retry.
bool retry = false;
int retryCount = 0;
do
{
retry = false;
try
{
result = await authContext.AcquireTokenAsync(graphResourceId, clientCred, userAssertion);
accessToken = result.AccessToken;
}
catch (AdalException ex)
{
if (ex.ErrorCode == "temporarily_unavailable")
{
// Transient error, OK to retry.
retry = true;
retryCount++;
Thread.Sleep(1000);
}
}
} while ((retry == true) && (retryCount < 1));
The [Authorize] decoration in the controller or whichever method we specify triggers the Owin security handler to validate the token and generates the claims.
I'm building two applications, WPF and Web API.
WPF connects to an identity server (now it's Azure AD) and get the access token then send it to my Web API to get the data.
How can I, in Web API, validate the access token to make sure it's correct. ?
Now I'm using Azure as I said but I should build to be able to validate any access token from any identity provider.
Is there an example or article explain this ?
Thanks
Encountered the same probelem.
I decided to use JWTToken
My architecture is the next one
Front <-> WebApi <-> Database
Front is in MVC4 WebApi2
The front will use a FormAuthentication method.
Save the JWT token once the user is fully logged, then send the Authentication Header on each request I do to the webapi.
The front part will only carry the encrypted JWT Token, nothing will be decrypted from it. Only send the token into the authentication http header tag.
On the webapi side each request are cached into DelegatingHandle, Check is the called method need to be authorized or not, validate the JWTToken then do what ever the webapi method does.
I cannot send you some part of my code because this is bellongs now to my company, but I can link you some Internet readings :)
1 - JWT
2- ASP.Net Web API with JWT (webapi handler)
You must then use the [Authorize] or [AllowAnonymous] tags on your webapi methods.
You can even create your own tag to handle all the Groups things.
If you have more questions, feel free to ask :)
I think this will answer to 99% of your security Questions.
I know this is one year old question, but I hope this answer will help other users :)
Client Side code
public async void Authenticate(string aadInstance, string tenant, string clientId, Uri redirectUri, string resourceId)
{
try
{
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
authContext = new AuthenticationContext(authority, new FileCache());
AuthenticationResult result = null;
try
{
result = await authContext.AcquireTokenSilentAsync (resourceId, clientId);
}
catch (AdalException ex)
{
if (ex.ErrorCode == AdalError.UserInteractionRequired || ex.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
result = await authContext.AcquireTokenAsync(resourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Always));
}
}
ticket = result.AccessToken;
user = result.UserInfo.DisplayableId.Split('#')[0];
}
catch (Exception ex)
{
ticket = "Error";
throw ex;
}
}
Server side code
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
private JwtSecurityToken Validate(string token)
{
string stsDiscoveryEndpoint = "https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration";
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration config = configManager.GetConfigurationAsync().Result;
TokenValidationParameters validationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKeys = config.SigningKeys, //.net core calls it "IssuerSigningKeys" and "SigningKeys"
ValidateLifetime = true
};
JwtSecurityTokenHandler tokendHandler = new JwtSecurityTokenHandler();
SecurityToken jwt;
var result = tokendHandler.ValidateToken(token, validationParameters, out jwt);
return jwt as JwtSecurityToken;
}
I try to get OpenID Connect running... A user of my Web API managed to get an Authorization Code of a OpenID Connect Provider. How am I supposed to pass this code to my ASP.NET Web API? How do I have to configure OWIN Middleware such that I can get an Access Token using the Authorization Code?
UPDATE:
A SPA uses AJAX for communicating with my web service (ASP.NET Web API). In my web service a use OWIN Middleware. I set OpenIDConnect as the authentication mechanism. When the web service is called for the first time it successfully redirected the user to the login page of the OpenID Connect Provider. The user could login and got an Authorization Code as a result. AFAIK this code could now be used (by my web service) to the an Access Token. However, I don't know how to get this code back to my web service (is this done using a header?) and then what to configure to get the Access Token. I guess I could call the token endpoint manually but I would like to take advantage of the OWIN component instead.
BenV already answered the question, but there's more to consider.
class partial Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// ...
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
Notifications = new OpenIdConnectAuthenticationNotifications() {
AuthorizationCodeReceived = (context) => {
string authorizationCode = context.Code;
// (tricky) the authorizationCode is available here to use, but...
return Task.FromResult(0);
}
}
}
}
}
Two problems:
First of all, authorizationCode will get expired quickly. There's no sense in storing it.
The second problem is that AuthorizationCodeReceived event will not get fired for any of the page reloads as long as authorizationCode is not expired and stored inside the session.
What you need to do is to call AcquireTokenByAuthorizationCodeAsync which will cache it and handle properly inside TokenCache.DefaultShare:
AuthorizationCodeReceived = (context) => {
string authorizationCode = context.Code;
AuthenticationResult tokenResult = await context.AcquireTokenByAuthorizationCodeAsync(authorizationCode, new Uri(redirectUri), credential);
return Task.FromResult(0);
}
Now, before every call to the resource, invoke AcquireTokenSilentAsync to get the accessToken (it will use TokenCache or silently use refreshToken ). If token is expired, it will raise AdalSilentTokenAcquisitionException exception (invoke access code renew procedure).
// currentUser for ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")
AuthenticationResult authResult = await context.AcquireTokenSilentAsync(resourceUri, credential, currentUser);
Calling AcquireTokenSilentAsync is very fast if token is cached.
Looks like the recommended approach is to use the AuthorizationCodeReceived event to exchange the Auth code for an Access Token. Vittorio has a blog entry that outlines the overall flow.
Here's an example from this sample app on GitHub of the Startup.Auth.cs code to set this up:
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = Authority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthorizationCodeReceived = (context) =>
{
var code = context.Code;
ClientCredential credential = new ClientCredential(clientId, appKey);
string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
AuthenticationContext authContext = new AuthenticationContext(string.Format("https://login.windows.net/{0}", tenantID), new EFADALTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(
code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, graphResourceID);
return Task.FromResult(0);
},
...
}
Note: The AuthorizationCodeReceived event is invoked only once when authorization really takes place. If the auth code is already generated and stored, this event is not invoked. You have to logout or clear cookies to force this event to take place.
This is now built into Microsoft.Owin 4.1.0 or later. You can use SaveTokens to make the id_token available and then RedeemCode to get an access token and make that available
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
ClientSecret = "redacted",
RedirectUri = postLogoutRedirectUri,
//This allows multitenant
//https://github.com/Azure-Samples/guidance-identity-management-for-multitenant-apps/blob/master/docs/03-authentication.md
TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false
},
Notifications = new OpenIdConnectAuthenticationNotifications()
{
AuthenticationFailed = (context) =>
{
return Task.FromResult(0);
}
},
SaveTokens = true,
// Required for the authorization code flow to exchange for tokens automatically
// using this means you will need to provide RedirectUri and ClientSecret
RedeemCode = true
}
);
You can then access the tokens through the HttpContext object
var result = Request.GetOwinContext().Authentication.AuthenticateAsync("Cookies").GetAwaiter().GetResult();
string idToken = result.Properties.Dictionary["id_token"];
string accessToken = result.Properties.Dictionary["access_token"];
Sources:
How to get access token from httpcontext using owin and Mvc 5
You need to bypass the default owin validation to do custom Authorization:
new OpenIdConnectAuthenticationOptions
{
...,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
},
TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = ClaimTypes.Role },
This line of code solved my issue. We need to validate the issuer to be false.