The current application that we are developing consists of 2 applications. A WebApi application, and a MVC frontend application. For the WebApi i added support for bearer token authorization via OWIN. These applications run as seperate websites within the same domain but with their own subdomains site.xxx.xxx, api.xxx.xxx
Authenticating to the WebAPi, f.e. with Postman, works as designed, the principal and identity objects, including the claims, are initialized properly.
The question arises when i want to login to the WEbApi from within the Mvc application.
Is there any way to get the ClaimsPrincipal and the ClaimsIdentity in our MVC application after logging in via the WebAPI via the /token url somewhat sharing the OWIN context, or should we implement the same OWIN authorization functionality inside the MVC application to create a seperate autorization "route"?
Yes, there is. Couple things to note
The token you get back from the web api will be encrypted by default. Your web application needs to decrypt this token to be able to extract the claims from the bearer token. For this, you have to have the same machine key on both of the servers (your webapi web.config and mvc web.config needs to have the same machine key)
Your MVC web app needs to wire up both bearer tokens and application cookies. Your startup.auth.cs might include something like this:
public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }
static Startup()
{
OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
}
public void ConfigureAuth(IAppBuilder app)
{
app.UseOAuthBearerAuthentication(OAuthBearerOptions);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
}
Now in your login method
//Assume that the token that you got from web api is in the variable called accessToken
//Decrypt this token first. If your machine keys are the same, the following line will work
var unencryptedToken = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(accessToken);
//Next, extract the claims identity from the token
var identity = unencryptedToken.Identity;
//Need to create a claims identity that uses a cookie (not a bearer token). An MVC app
//knows how to deal with a claims identity using an application cookie, but doesn't know
//how to deal with a claims identity using a bearer token. So this is a translation step
//from a web api authentication mechanism to the mvc authentication mechanism
var id = new ClaimsIdentity(identity.Claims, DefaultAuthenticationTypes.ApplicationCookie);
//At this moment, your new claims identity using an application cookie is ready, but we still
//need to sign in. Use the OWIN Auth manager from the context to sign in. This will create
//the application cookie and correctly populate User.IsAuthenticated(). From now on, you are
//logged in
AuthenticationManager.SignIn(id);
Related
Context
I have a Service Provider (SP) based on IdentityServer 4 and Sustainsys.Saml2.AspNetCore2 that is configured to use Azure as an IdP (SAML2).
I also have a SPA with an api that connects to my SP (with oidp) to identify my user. The api then creates a JWT for my user to use.
I can login my user correctly.
Question
My issue comes with the logout. I want to use the logout url parameter of Azure to notify my SP about the logout. I manage to see the SAML Logout Request as a string when I configure an endpoint of mine but I can't exploit it and parsing it manually does't seem right.
Is there an existing endpoint that would come with my dependencies that I missed?
The goal here is to revoke all my user's application sessions (the apps to which my user is connected throug my SP).
Configuration
Idp configuration in the SP (called in Startup.cs).
The Saml2AuthModel comes from a config file.
public static AuthenticationBuilder AddSaml2Auth(this AuthenticationBuilder builder, Saml2AuthModel saml2AuthModel)
{
builder.AddSaml2(saml2AuthModel.Scheme, saml2AuthModel.DisplayName ?? saml2AuthModel.Scheme, options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SPOptions.EntityId = new EntityId(saml2AuthModel.ServiceProviderEntityId);
options.SPOptions.ModulePath = "/" + saml2AuthModel.ModulePath ?? saml2AuthModel.Scheme ?? options.SPOptions.ModulePath;
var idp = new IdentityProvider(
new EntityId(saml2AuthModel.IdentityProviderEntityId),
options.SPOptions
);
idp.MetadataLocation = saml2AuthModel.IdentityProviderMetadataLocation;
options.IdentityProviders.Add(idp);
});
return builder;
}
The Sustainsys.Saml2 library has support for single logout. To enable it, you need to set up a service signing key. The reason is that logout requests and responses should be signed. So the library doesn't expose the logout endpoints if it has no signing keys available.
I have a service that gets an access token from Azure AD. I have an API that I would like to accept that token as authorization.
My service code to call the API is
HttpClient client = new HttpClient()
{
BaseAddress = new Uri("https://localhost:44372/")
};
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, client.BaseAddress + "api/todolist");
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.SendAsync(request);
The response I get back is a 401 - Unauthorized.
I have a feeling that the issue is in the API ConfigureServices function; specifically (this was taken from an example, so I don't really know what it means yet):
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.TokenValidationParameters.RoleClaimType = "roles";
});
I'm new to Azure and authentication in general so I don't know what options are available or appropriate. I also am not sure how to set up the applications in Azure to allow this. I have the application id of the service set up as an Authorized client application of the API; it is also listed int the knownClientApplications in the API manifest.
There are just so many knobs to turn, I have no idea where to go from here. If anyone can let me know some things to try, that would be outstanding.
Thanks
Here is a code sample on how to call a web API in an ASP.NET Core web app using Azure AD:
https://learn.microsoft.com/en-us/samples/azure-samples/active-directory-dotnet-webapp-webapi-openidconnect-aspnetcore/calling-a-web-api-in-an-aspnet-core-web-application-using-azure-ad/
This sample contains a web API running on ASP.NET Core 2.0 protected by Azure AD. The web API is accessed by an ASP.NET Core 2.0 web application on behalf of the signed-in user. The ASP.NET Web application uses the OpenID Connect middleware and the Active Directory Authentication Library (ADAL.NET) to obtain a JWT bearer token for the signed-in user using the OAuth 2.0 protocol. The bearer token is passed to the web API, which validates the token and authorizes the user using the JWT bearer authentication middleware.
We have a SharePoint publishing site with anonymous access hosted on the internet. As per out latest requirements, we need to implement user login (AzureAD, Microsoft personal and work accounts, and more).
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
As per the documentation here, we want to implement this using Web API to get the secure information from the database. We are thinking about using MSAL.js file for user login and logout on the SharePoint and after getting a bearer token we can call the Web API for the additional data from our database.
Standalone Web APIs restriction: “You can use the v2.0 endpoint to build a Web API that is secured with OAuth 2.0. However, that Web API can receive tokens only from an application that has the same Application ID. You cannot access a Web API from a client that has a different Application ID. The client won't be able to request or obtain permissions to your Web API.”
How can we create two applications with same application ID at App Registration Portal? Or should we use the same application ID at SharePoint and Web API’s end?
There is no need to register two application, you only need to one register application. After you register the application, you can using the MSAL library below to get the token to call the web API:
<script class="pre">
var userAgentApplication = new Msal.UserAgentApplication("e5e5f2d3-4f6a-461d-b515-efd11d50c338", null, function (errorDes, token, error, tokenType) {
// this callback is called after loginRedirect OR acquireTokenRedirect (not used for loginPopup/aquireTokenPopup)
})
userAgentApplication.loginPopup(["user.read"]).then(function (token) {
var user = userAgentApplication.getUser();
console.log(token);
// signin successful
}, function (error) {
// handle error
});
</script>
And to protect the web API, you can use the same app and refer the code below:
public void ConfigureAuth(IAppBuilder app)
{
var tvps = new TokenValidationParameters
{
// The web app and the service are sharing the same clientId
ValidAudience = "e5e5f2d3-4f6a-461d-b515-efd11d50c338",
ValidateIssuer = false,
};
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
// metadata document.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
});
}
I've successfully setup a custom authentication process with Azure Mobile Services and my Windows Phone 8.1 app (following the guide here)
I'm now creating an MVC5 single page application (SPA) to manage the admin side of things for the system. I'm relatively new to MVC5 and just need a little help to get started with performing a login just like in my phone app?
Currently my phone app performs a login by
App.MobileService.CurrentUser = await AuthenticateAsync(this.textBox_email.Text, textBox_password.Password);
which does
private async Task<MobileServiceUser> AuthenticateAsync(string username, string password)
{
// Call the CustomLogin API and set the returned MobileServiceUser
// as the current user.
var user = await App.MobileService
.InvokeApiAsync<LoginRequest, MobileServiceUser>(
"CustomLogin", new LoginRequest()
{
UserName = username,
Password = password
});
return user;
}
this all works well so I guess the question is how do I do make a call to my customer authentication API in the same way in MVC5 and set the user context if successful?
Startup.Auth.cs:
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Enable the application to use a cookie to store information for the signed in user
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login")
});
// Use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Uncomment the following lines to enable logging in with third party login providers
//app.UseMicrosoftAccountAuthentication(
// clientId: "",
// clientSecret: "");
//app.UseTwitterAuthentication(
// consumerKey: "",
// consumerSecret: "");
//app.UseFacebookAuthentication(
// appId: "",
// appSecret: "");
//app.UseGoogleAuthentication();
}
Let me know if I'm missing any info or detail.
Thanks!
Unfortunately this is not easy to do in Mobile Services. While you could achieve login using the Mobile Services HTML/JS SDK (served in an MVC view), this will not set the user context.
Because of Mobile Services incompatibility with MVC (addressed in the new Mobile Apps product), you won't be able to rely on that SDK. Unfortunately that means writing custom middleware/filters.
The easiest solution is probably to package your username/password validation and storage logic into code that can be shared by your Mobile Services project and your MVC project. The MVC project would need to take the validated user and issue a session cookie which is then read by a custom middleware or filter.
Writing an AuthorizationFilter implementation will be much easier than an OWIN middleware, so I would recommend that approach. Check if the cookie is present and valid, and if so set the user context.
I'm creating an application in 2 parts. On one server is a .net Webapi2 using Owin. On another server is an MVC5 website with currently no login that will act as a front end for the api. It would also be a nice selling point to show that the app itself is an example of what a client can develop since it relies on the same api. I put the user authentication stuff in the api because I need 3rd parties to be able to develop their own front end apps using the api.
What I'm trying to accomplish (in theory)
I need to have a user submit their login information on the front end it will authenticate them via the resourceownergrant type and recieve results that allow the front end to create a cookie that includes the accesstoken and the identityuser / roles. As long as this cookie exists the mvc app would make calls to the api using the accesstoken. The MVC app and API would both be able to use the [Authorize] attribute.
What I have so far
I have the api up and working, I can post "grant_type=password&username=testuser&password=password123" and I receive something like this in json
{
"access_token":"-longasstokenhere-",
"token_type":"bearer",
"expires_in":1209599,
"userName":"testuser",
".issued":"Thu, 03 Apr 2014 16:21:06 GMT",
".expires":"Thu, 17 Apr 2014 16:21:06 GMT"
}
the web api's response also has a header of
set-cookie: -Long-assserializedcookiestuffhere-
My question is how to connect my mvc app.
in my mvc5 app I've got a startup for owin with this set in the configureauth
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
CookieName = "MyCookie",
LoginPath = new PathString("/Account/Login")
});
I have the [Authorize] attribute set on a test page that when I visit, it correctly redirects me to the login page. The piece I'm missing is how to make it so that clicking login makes the website post back to the api and create an Owin Cookie that the website will use to allow a user past the [authorize] attibute. I would also like it to contain the identityuser as well so the web app will automatically have the user info without having to post back to the api for it on every call. I dont know if I can grab the cookie from the return result somehow or what.
Also if theirs a better way I'm open to suggestions.
Heeelp!?
I had the exact same requirement. I tried to get the MVC5 CookieAuthentication to work, but it wouldn't let me use my own cookie value. But, your WebAPI should not return a set-cookie. WebAPI should be RESTful, and require the client to pass a bearer token on every request.
So, here's my solution. The username and password are sent to an external API, which return a JSON Web Token. The token from the API is stored in a cookie. You could do that in JavaScript, or in an MVC Account controller. That cookie is checked by the MVC app, and if it exists, the cookie indicates proof that the user is logged in to the MVC app, too.
In JavaScript, you pull that token from the cookie, and add it to all requests to the API in the Authorization header as Bearer token. Also, the MVC app can use the value of the cookie (the token) to access all of the user's claims. To logout, just expire the cookie.
First, wire up the app to use Bearer token, with our custom provider
// Api controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AllowedAudiences = audienceId.ToArray(),
IssuerSecurityTokenProviders = providers.ToArray(),
Provider = new CookieOAuthBearerProvider("MyCookieName")
{
LoginPath = new PathString("/Account/Login")
}
}
);
Now, I had to use a custom provider, because the Bearer token is stored in the cookie, not in the standard header. I also need to redirect to a login page, rather that simply issuing a 401.
public class CookieOAuthBearerProvider : IOAuthBearerAuthenticationProvider
{
public PathString LoginPath {get; set;}
public string CookieName { get; set; }
public CookieOAuthBearerProvider(string cookieName)
{
if(string.IsNullOrWhiteSpace(cookieName)) {
throw new ArgumentNullException("cookieName");
}
else {
this.CookieName = cookieName;
};
}
public Task ApplyChallenge(OAuthChallengeContext context)
{
if (this.LoginPath.HasValue)
{
context.Response.Redirect(this.LoginPath.Value);
}
return Task.FromResult<object>(null);
}
public Task RequestToken(OAuthRequestTokenContext context)
{
string token = context.Request.Cookies[this.CookieName];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.FromResult<object>(null);
}
public Task ValidateIdentity(OAuthValidateIdentityContext context)
{
// prove that the cookie token matches this site using context.Ticket.Identity
return Task.FromResult<object>(null);
}
}
Then, anywhere else in your MVC app, you can get the Claims by simply saying:
ClaimsPrincipal.Current