I am using the following code to successfully get a token from my MVC web app. However I am unsure how to retrieve the claims that I have added. Should they be returned in the same response as my token?
Thanks!
Startup.cs:
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.Audience = "resource_server";
options.Authority = "https://www.example.com/";
options.RequireHttpsMetadata = false;
});
app.UseOpenIdConnectServer(options =>
{
options.ApplicationCanDisplayErrors = true;
options.AllowInsecureHttp = false;
options.Provider = new AuthorizationProvider();
options.TokenEndpointPath = "/connect/token";
});
Adding claims:
identity.AddClaim("custom_claim", "value", "token id_token");
foreach (string role in await userManager.GetRolesAsync(user))
{
identity.AddClaim(ClaimTypes.Role, role, "id_token token");
}
This is my PostAsync result:
{"resource":"resource_server","scope":"openid profile","token_type":"bearer","access_token":"eyJhbGciOiJSU....","expires_in":"3600"}
Should they be returned in the same response as my token?
Since you're specifying both the id_token and token destinations, your claims should be copied in both the access token and the identity token. You should be able to use any JSON parser to extract the claims you're looking for from the access_token/id_token properties.
Two remarks:
In ASOS beta4, you had to explicitly add scope=openid to your token request to get back an identity token, even if you called ticket.SetScopes("openid"), which probably explains why there's no id_token property in the response you shared. This policy was relaxed in the next version.
In ASOS beta4 (for ASP.NET Core RC1), access tokens were serialized using the JWT format. This is no longer true with the beta5 version, that uses an encrypted format by default. Don't try to read them from the client application: instead, use the id_token, which is meant to be consumed by the client app.
Related
I am trying to add multiple ways to authenticate users in our app. All but one are working flawlessly. I am able to log in with ASP.NET Core Identity, GitHub, Azure AD, and even API auth, but JWT is giving me a bit of a headache as I always get a 401 response when I pass in an bearer token to the authorization header.
This might have something to do with a custom middleware class that works on this header:
app.UseMiddleware<JwtAuthMiddleware>()
.UseAuthentication()
.UseAuthorization()
The middleware class in question:
public class JwtAuthMiddleware
{
private readonly RequestDelegate _next;
public JwtAuthMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null)
{
string jwtEncodedString = authHeader[7..]; // Fetch token without Bearer
JwtSecurityToken token = new(jwtEncodedString: jwtEncodedString);
ClaimsIdentity identity = new(token.Claims, "jwt");
context.User = new ClaimsPrincipal(identity);
}
return _next(context);
}
}
The identity setup is fairly simple.
services
.AddAuthentication()
.AddCookie(/*config*/)
.AddGitHub(/*config*/)
.AddJwtBearer(/*config*/)
.AddAzureAd(/*config*/);
string[] authSchemes = new string[]
{
IdentityConstants.ApplicationScheme,
CookieAuthenticationDefaults.AuthenticationScheme,
GitHubAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme,
"InHeader"
};
AuthorizationPolicy authnPolicy = new AuthorizationPolicyBuilder(authSchemes)
.RequireAuthenticatedUser()
.Build();
services.AddAuthorization(options =>
{
options.FallbackPolicy = authnPolicy;
});
I also set a filter in AddMvc:
.AddMvc(options =>
{
AuthorizationPolicy policy = new AuthorizationPolicyBuilder(authSchemes)
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
JWT works when options.FallbackPolicy is not set. Otherwise, I get a 401 response. Inspecting the DenyAnonymousAuthorizationRequirement in debug mode confirms this as there is no principal set with the right claims filled out. So it looks like context.User is ignored or reset.
Ideally, I would want to get rid of JwtAuthMiddleware altogether, but I still have to figure out how to combine JWT with cookie authentication in this particular setup. Any thoughts?
You don't need to use this custom JwtAuthMiddleware and define the custom ClaimsPrincipal like that. The problem is that you are just extracting the claims from the token. Setting the ClaimsPrincipal like that wouldn't automatically authenticate the user i.e. Context.User.Identity.IsAuthenticated would be false. Hence you get 401.
The token needs to be validated. You are already using AddJwtBearer which you can customize the code like below if you haven't done that.
JWT bearer authentication performs authentication automatically by extracting and validating a JWT token from the Authorization request header. However, you need to set the TokenValidationParameters which tells the handler what to validate.
services
.AddAuthentication()
.AddJwtBearer("Bearer", options =>
{
// you don't have to validate all of the parameters below
// but whatever you need to, but see the documentation for more details
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config.JwtToken.Issuer, // presuming config is your appsettings
ValidateAudience = true,
ValidAudience = config.JwtToken.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JwtToken.SigningKey))
};
})
However, there is a caveat. When combining multiple authentication mechanisms, you need to utilise multiple authentication schemes. They play a role in defining the logic to select the correct authentication mechanism. That is because you can call AddJwtBearer or AddCookie multiple times for different scenarios. If the scheme name is not passed, it would try to utilize the default scheme and the authentication will fail, as it would have no way of knowing which mechanism to use JWT or AzureAd as they both use the Bearer token.
You mentioned that JWT works when FallbackPolicy is not set but you get 401 otherwise. That is because your authorization policy requires a user to be authenticated. The user was never authenticated as I mentioned in the beginning. If FallbackPolicy is not set it would work in the context of JWT being passed in the alright, but there is no requirement to check e.g user is authenticated and has a specific claim or role, so it works.
You would need to define an authentication policy scheme and set the ForwardDefaultSelector property of the PolicySchemeOptions. ForwardDefaultSecltor is used to select a default scheme for the current request that authentication handlers should forward all authentication operations to by default.
So basically you need to set the ForwardDefaultSecltor delegate which uses some logic to forward the request to the correct scheme to handle the authentication.
So the above code would change to:
// scheme names below can be any string you like
services
.AddAuthentication("MultiAuth") // virtual scheme that has logic to delegate
.AddGitHub(GitHubAuthenticationDefaults.AuthenticationScheme, /*config*/) // uses GitHub scheme
.AddAzureAd("AzureAd", /*config*/) // uses AzureAd scheme
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, /*config*/) // uses Cookies scheme
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { // above code for AddJwtBearer }) // uses Bearer scheme
// this is quite important to use the same virtual scheme name below
// which was used in the authentication call.
.AddPolicyScheme("MultiAuth", null, options =>
{
// runs on each request should return the exact scheme name defined above
options.ForwardDefaultSelector = context =>
{
// if the authorization header is present, use the Bearer scheme
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
{
return "Bearer";
}
// custom logic for GitHub
...
return GitHubAuthenticationDefaults.AuthenticationScheme;
// custom logic for AzureAd
...
return "AzureAd";
// otherwise fallback to Cookies or whichever is the default authentication scheme
return CookieAuthenticationDefaults.AuthenticationScheme;
};
});
That way, you just remove the JwtAuthMiddleware. AddJwtBearer would automatically add the claims in the Claims object when the token is validated, which you can then use to define custom authorization policies to authorize the user.
I hope that helps.
#Shazad Hassan made some good points in his answer. The custom middleware was in fact not necessary in the end because the System.IdentityModel.Tokens.XXXX assemblies were throwing errors that the tokens were invalid. I never really knew the token was invalid because the middleware assumed it was correct (and content-wise, it was), while all this time the context was running for an unauthenticated user, which became apparent when I applied the fallback policy.
So I rewrote the token generation code and now I no longer get this issue, and I can safely remove the custom middleware. So far so good.
While I still have to bone up on authentication schemes and all that, for the moment it doesn't even seem necessary to have multiple calls to AddAuthentication because this works:
services
.AddAuthentication()
.AddCookie(configureCookieAuthOptions)
.AddGitHub(configureGithub)
.AddAzureAd(config)
.AddJwtBearer(configureJwtBearerOptions)
.AddApiKeyInHeader<ApiKeyProvider>("InHeader", options =>
{
options.Realm = "My realm";
options.KeyName = "X-API-KEY";
});
With this, I am able to log in with every method in the list. Looks promising, but might need to be reviewed by someone who knows the ins and outs of authentication in ASP.NET Core.
So: some clients will be sending a cookie, and some will be sending Authorize header with a bearer token.
These are authentication schemes that you set up with AddAuthentication.
After that you need to set up authorization (AddAuthorization) to grant access to users who are authentication with either of these schemes.
(Authentication means checking that you are who you say you are (users), authorization means what you are and aren't allowed to do (roles) -- it's confusing.)
I am trying to create Azure AD provisioning for our Saas product (using scim2).
I want multiple customers to be able to connect with their Azure AD tenant.
Microsoft has reference code here: https://github.com/AzureAD/SCIMReferenceCode
However, that is setup to only allow one tenant and also to not use the "secret token" that you set up in azure ad. Even tho the comment specifically states the secret token should not be left empty for production.
Here is the important piece of code from the reference project
// Leave the optional Secret Token field blank
// Azure AD includes an OAuth bearer token issued from Azure AD with each request
// The following code validates the Azure AD-issued token
// NOTE: It's not recommended to leave this field blank and rely on a token generated by Azure AD.
// This option is primarily available for testing purposes.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = this.configuration["Token:TokenIssuer"];
options.Audience = this.configuration["Token:TokenAudience"];
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
// NOTE: You can optionally take action when the OAuth 2.0 bearer token was validated.
return Task.CompletedTask;
},
OnAuthenticationFailed = AuthenticationFailed
};
});
With that code it works assuming Token:TokenIssuer setting is https://sts.windows.net/<tenant_id>/ where tenant_id is the actual tenant id and TokenAudience is 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 (non gallery app).
But it only works if I leave the "Secret token" empty when I set it up in azure ad (non gallery app under Entrprise applications).
I have tried all sorts of things, adding OnChallenge tells me a challenge is sent if I set the "Secret token" but beyond that I am not getting much further.
Any sample code for handling multiple tenants and secret tokens here would be amazing
Update:
Using options.TokenValidationParameters.IssuerValidator I can validate the issuer and thus make that work with multiple tenants. What I really can't get past right now is making a call work when I enter a "Secret token" here: (see picture)
So I figured out that what they want is a JWT token in that field that I generate.
So first I created a method that generates a web token
private string GenerateJSONWebToken()
{
// Create token key
SymmetricSecurityKey securityKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Token:TokenSigningKey"]));
SigningCredentials credentials =
new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
// Set token expiration
DateTime startTime = DateTime.UtcNow;
DateTime expiryTime = startTime.AddMinutes(120);
// Generate the token
JwtSecurityToken token =
new JwtSecurityToken(
configuration["Token:TokenIssuer"],
configuration["Token:TokenAudience"],
null,
notBefore: startTime,
expires: expiryTime,
signingCredentials: credentials);
string result = new JwtSecurityTokenHandler().WriteToken(token);
return result;
}
In my appsettings.json I added
{
"Logging": {
...
},
"Token": {
"TokenAudience": "xxx-xxx-xxx-xxx",
"TokenIssuer": "https://sts.windows.net/yyyy-yyyy-yyyy/",
"TokenSigningKey": "zzz"
}
}
Token Audience I set to 8adf8e6e-67b2-4cf2-a259-e3dc5476c621 as can be read about here https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups.
TL;DR The audience for the token will be the application template ID for the application in the gallery, the application template ID for all custom apps is 8adf8e6e-67b2-4cf2-a259-e3dc5476c621
yyyy part of TokenIssuer is the tenant id of the azure ad tenant
zzz from signing key is simply a key of your choosing.
Now finally I generated a token containing the values from appsettings.json.
I then pasted this key into the "Secret token" field in Azure AD.
Finally, how to make this multi tenant (my next steps)
Remove Token:TokenIssuer from appsettings.json
When you call GenerateJSONWebToken send in the client Azure AD tenant ID and use that instead of static value from appsettings.json (Either your client will give you this, or you have it from connecting your app to them)
In startup.cs notice I have already implemented IssuerValidator. Update this to validate not against appsettings.json but your data store.
You can visit Managing user account provisioning for enterprise apps in the Azure portal know more about the setup:
I'm struggling to implement a custom auth flow with OAuth and JWT.
Basically it should go as follows:
User clicks in login
User is redirect to 3rd party OAuth login page
User logins into the page
I get the access_token and request for User Info
I get the user info and create my own JWT Token to be sent back and forth
I have been following this great tutorial on how to build an OAuth authentication, the only part that differs is that Jerrie is using Cookies.
What I Have done so far:
Configured the AuthenticationService
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "3rdPartyOAuth";
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie() // Added only because of the DefaultSignInScheme
.AddJwtBearer(options =>
{
options.TokenValidationParameters = // Ommited for brevity
})
.AddOAuth("3rdPartyOAuth", options =>
{
options.ClientId = securityConfig.ClientId;
options.ClientSecret = securityConfig.ClientSecret;
options.CallbackPath = new PathString("/auth/oauthCallback");
options.AuthorizationEndpoint = securityConfig.AuthorizationEndpoint;
options.TokenEndpoint = securityConfig.TokenEndpoint;
options.UserInformationEndpoint = securityConfig.UserInfoEndpoint;
// Only this for testing for now
options.ClaimActions.MapJsonKey("sub", "sub");
options.Events = new OAuthEvents
{
OnCreatingTicket = async context =>
{
// Request for user information
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
context.RunClaimActions(user);
}
};
});
Auth Controller
[AllowAnonymous]
[HttpGet("login")]
public IActionResult LoginIam(string returnUrl = "/auth/loginCallback")
{
return Challenge(new AuthenticationProperties() {RedirectUri = returnUrl});
}
[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
// Here is where I expect to get the user info, create my JWT and send it back to the client
return Ok();
}
Disclaimer: This OAuth flow is being incorporated now. I have a flow for creating and using my own JWT working and everything. I will not post here because my problem is before that.
What I want
In Jerrie's post you can see that he sets DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;. With that, when the /auth/loginCallback is reached I have the user claims in the HttpContext.
The problem is my DefaultAuthenticateScheme is set to JwtBearersDefault and when the loginCallback is called I can't see the user claims nowhere in the Request.
How can I have access to the information gained on the OnCreatingTicketEvent in my callback in this scenario?
Bonus question: I don't know much about OAuth (sure that is clear now). You may have noted that my options.CallbackPath differs from the RedirectUri passed in the Challenge at the login endpoint. I expected the option.CallbackPath to be called by the 3rd Part OAuth provider but this is not what happens (apparently). I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right? The Callback is used for nothing but a match configuration? I can even comment the endpoint CallbackPath points to and it keep working the same way...
Thanks!
Auth
As Jerrie linked in his post, there is a great explanation about auth middlewares:
https://digitalmccullough.com/posts/aspnetcore-auth-system-demystified.html
You can see a flowchart in the section Authentication and Authorization Flow
The second step is Authentication middleware calls Default Handler's Authenticate.
As your default auth handler is Jwt, the context is not pupulated with the user data after the oauth flow,
since it uses the CookieAuthenticationDefaults.AuthenticationScheme
Try:
[AllowAnonymous]
[DisableRequestSizeLimit]
[HttpGet("loginCallback")]
public IActionResult IamCallback()
{
//
// Read external identity from the temporary cookie
//
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
if (result?.Succeeded != true)
{
throw new Exception("Nein");
}
var oauthUser = result.Principal;
...
return Ok();
}
Great schemes summary: ASP.NET Core 2 AuthenticationSchemes
You can persist your user with
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationhttpcontextextensions.signinasync?view=aspnetcore-2.2
Bonus
I did have to set the CallbackPath to the same value I have set in the OAuth provider configuration (like Jerries tutorial with GitHub) for it to work. Is that right?"
Yes.
For security reasons, the registered callback uri (on authorization server) and the provided callback uri (sent by the client) MUST match.
So you cannot change it randomly, or if you change it, you have to change it on the auth server too.
If this restriction was not present, f.e. an email with a mailformed link (with modified callback url) could obtain grant.
This is called Open Redirect, the rfc refers to it too: https://www.rfc-editor.org/rfc/rfc6749#section-10.15
OWASP has a great description: https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md
I can even comment the endpoint CallbackPath points to and it keep working the same way..."
That is because your client is trusted (you provide your secret, and you are not a fully-frontend Single Page App). So it is optional for you to send the callback uri.
But IF you send it, it MUST match with the one registered on the server. If you don't send it, the auth server will redirect to the url, that is registered on its side.
https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
redirect_uri
OPTIONAL. As described in Section 3.1.2.
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2
The authorization server redirects the user-agent to the
client's redirection endpoint previously established with the
authorization server during the client registration process or when
making the authorization request.
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.2
The authorization server MUST require the following clients to register their redirection endpoint:
Public clients.
Confidential clients utilizing the implicit grant type.
Your client is confidential and uses authorization code grant type (https://www.rfc-editor.org/rfc/rfc6749#section-1.3.1)
https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2.3
If multiple redirection URIs have been registered, if only part of
the redirection URI has been registered, or if no redirection URI has
been registered, the client MUST include a redirection URI with the
authorization request using the "redirect_uri" request parameter.
You have registered your redirect uri, that's why the auth server does not raise an error.
change [AllowAnonymous]
to [Authorize]
on the 'loginCallback' endpoint (AuthController.IamCallback method)
We have a web app built on Asp.Net core. It doesn't contain any authentication middleware configured in it.
We are hosting on Azure App Service and using the Authentication/Authorization option (EasyAuth) to authenticate against Azure AD.
The authentication works well - we get the requisite headers inserted and we can see the authenticated identity at /.auth/me. But the HttpContext.User property doesn't get populated.
Is this a compatibility issue for Asp.Net core? Or am I doing something wrong?
I've created a custom middleware that populates the User property until this gets solved by the Azure Team.
It reads the headers from the App Service Authentication and create a a user that will be recognized by the [Authorize] and has a claim on name.
// Azure app service will send the x-ms-client-principal-id when authenticated
app.Use(async (context, next) =>
{
// Create a user on current thread from provided header
if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
{
// Read headers from Azure
var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];
// Create claims id
var claims = new Claim[] {
new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
};
// Set user in current context as claims principal
var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
identity.AddClaims(claims);
// Set current thread user to identity
context.User = new GenericPrincipal(identity, null);
};
await next.Invoke();
});
Yes, this is a compatibility issue. ASP.NET Core does not support flowing identity info from an IIS module (like Easy Auth) to the app code, unfortunately. This means HttpContext.User and similar code won't work like it does with regular ASP.NET.
The workaround for now is to invoke your web app's /.auth/me endpoint from your server code to get the user claims. You can then cache this data as appropriate using the x-ms-client-principal-id request header value as the cache key. The /.auth/me call will need to be properly authenticated in the same way that calls to your web app need to be authenticated (auth cookie or request header token).
I wrote a small basic middleware to do this. It will create an identity based off of the .auth/me endpoint. The identity is created in the authentication pipeline so that [authorize] attributes and policies work with the identity.
You can find it here:
https://github.com/lpunderscore/azureappservice-authentication-middleware
or on nuget:
https://www.nuget.org/packages/AzureAppserviceAuthenticationMiddleware/
Once added, just add this line to your startup:
app.UseAzureAppServiceAuthentication();
The following code decrypts the AAD token from the Azure App Service HTTP header and populates HttpContext.User with the claims. It's rough as you'd want to cache the configuration rather than look it up on every request:
OpenIdConnectConfigurationRetriever r = new OpenIdConnectConfigurationRetriever();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.Endpoint, r);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys.ToList(),
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateLifetime = true,
ClockSkew = new TimeSpan(0, 0, 10)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
string token = context.Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
if (!String.IsNullOrWhiteSpace(token))
{
principal = handler.ValidateToken(token, tokenValidationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null) { throw new ArgumentException("Invalid JWT"); }
if (principal != null)
{
context.User.AddIdentities(principal.Identities);
}
}
It only works for Azure AD. To support other ID providers (Facebook, Twitter, etc) you'd have to detect the relevant headers and figure out how to parse each provider's token. However, it should just be variations on the above theme.
You can give this library a try. I faced a similar problem and created this to simplify the use.
https://github.com/dasiths/NEasyAuthMiddleware
Azure App Service Authentication (EasyAuth) middleware for ASP.NET
CORE with fully customizable components with support for local
debugging
It hydrates the HttpContext.User by registering a custom authentication handler. To make things easier when running locally, it even has the ability to use a json file to load mocked claims.
I am using ASP.Net 5 MVC 6 with JWT tokens that are created while the user is on another site which this site is a subdomain of. My goal is to pass the token along with the request to this subdomain. If a user happens to try to come to this subdomain url without the proper token in the header then I want to redirect them to the main site login page.
After much frustration with the newest RC-1 release and using JWT tokens with a SecureKey instead of certificates. I finally got my code working by using the RC-2 nightly build version. Now my problem is that I want to be able to redirect to an outside url in the case of unauthenticated users. Here is an example of my authentication code:
var key = "mysupersecretkey=";
var encodedkey2 = Convert.FromBase64String(key);
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(encodedkey2);
options.TokenValidationParameters.ValidIssuer = "https://tv.alsdkfalsdkf.com/xxx/yyy";
options.TokenValidationParameters.ValidateIssuer = true;
options.TokenValidationParameters.ValidAudience = "https://www.sdgfllfsdkgh.com/";
options.TokenValidationParameters.ValidateAudience = true;
options.Configuration = new Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration()
{
Issuer = "https://tv.sdfkaysdf.com/xxx/yyy"
};
});
now I see other examples which are using OpedId and they have it pretty easy , there is a parameter called RedirectUrl
app.UseOpenIdConnectAuthentication(options => {
...
options.RedirectUri = "https://localhost:44300";
...
});
any idea how to set RedirectUrl when using JwtBearerAuthentication ???
There's no such property for a simple reason: the JWT bearer middleware (like the more generic OAuth2 middleware in Katana) has been designed for API authentication, not for interactive authentication. Trying to trigger a redirection in this case wouldn't make much sense for headless HTTP clients.
That said, it doesn't mean that you can't redirect your unauthenticated users at all, at some point. The best way to handle that is to catch the 401 response returned by the JWT middleware at the client level and redirect the user to the appropriate login page. In JS applications for instance, this is usually done using an HTTP interceptor.
If you're really convinced breaking the OAuth2 bearer specification is the right thing to do, you can do that using the OnChallenge notification:
app.UseJwtBearerAuthentication(options => {
options.Events = new JwtBearerEvents {
OnChallenge = context => {
context.Response.Redirect("http://localhost:54540/login");
context.HandleResponse();
return Task.FromResult(0);
}
};
});