I'm implementing a Web API 2 service architecture in my .NET web application. The client consuming the requests is pure javascript, no mvc/asp.net. I'm using OWIN to try to enable token authentication per this article OWIN Bearer Token Authentication with Web API Sample. I seem to be missing something with the authentication step after its authorized.
My login looks like:
[HttpPost]
[AllowAnonymous]
[Route("api/account/login")]
public HttpResponseMessage Login(LoginBindingModel login)
{
// todo: add auth
if (login.UserName == "a#a.com" && login.Password == "a")
{
var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, login.UserName));
AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
var currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(30));
DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(new
{
UserName = login.UserName,
AccessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket)
}, Configuration.Formatters.JsonFormatter)
};
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
It returns
{
accessToken: "TsJW9rh1ZgU9CjVWZd_3a855Gmjy6vbkit4yQ8EcBNU1-pSzNA_-_iLuKP3Uw88rSUmjQ7HotkLc78ADh3UHA3o7zd2Ne2PZilG4t3KdldjjO41GEQubG2NsM3ZBHW7uZI8VMDSGEce8rYuqj1XQbZzVv90zjOs4nFngCHHeN3PowR6cDUd8yr3VBLdZnXOYjiiuCF3_XlHGgrxUogkBSQ",
userName: "a#a.com"
}
Then I try to set the HTTP header Bearer on further requests in AngularJS like:
$http.defaults.headers.common.Bearer = response.accessToken;
to a API like:
[HttpGet]
[Route("api/account/profile")]
[Authorize]
public HttpResponseMessage Profile()
{
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(new
{
UserName = User.Identity.Name
}, Configuration.Formatters.JsonFormatter)
};
}
but no matter what I do this service is 'unauthorized'. Am I missing something here?
Resolved by setting header 'Authorization' with Bearer + token like:
$http.defaults.headers.common["Authorization"] = 'Bearer ' + token.accessToken;
You can configure it with your angular application module. So authorization token will be set as header for every http request.
var app = angular.module("app", ["ngRoute"]);
app.run(function ($http) {
$http.defaults.headers.common.Authorization = 'Bearer ' + token.accessToken;
});
Related
I want to authenticate in my application using Google OpenID. I added authentication in Swagger, but after successfully redirection, login and redirection back to my Swagger, requests are only send access_token in header, but not id_token which is supposed to include e.g. user email (I used https://www.googleapis.com/auth/userinfo.email scope). Is there an option to get this id_token to correctly authenticate in .NET app? I'm using implicit flow right now. There is my Swagger configuration:
services.AddSwaggerGen(c =>
{
c.AddSecurityRequirement(new OpenApiSecurityRequirement() {
{
new OpenApiSecurityScheme {
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "oauth2"
},
Scheme = "oauth2",
Name = "authorization",
In = ParameterLocation.Header
},
new List <string> ()
}
});
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit= new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"https://accounts.google.com/o/oauth2/auth"),
TokenUrl = new Uri($"https://oauth2.googleapis.com/token"),
Scopes = new Dictionary<string, string>
{
{
$"https://www.googleapis.com/auth/userinfo.email",
"Get email"
}
}
}
}
});
The request looks following:
Request with access_token
I read that there is a possibility to change default authorization parameter in Swagger. So after adding following configuration:
Extensions = new Dictionary<string, IOpenApiExtension>
{
{
"x-tokenName", new OpenApiString("id_token")
}
}
result was still inappropriate:
Request with id_token
I am building an ASP.NET (v4.8) Web application that will be hosted as an Azure App Service, but for now we are on localhost.
I am configured for Azure AD successfully and I am receiving an authorization code because I configured my app service to send the access token. The app registration has ONLY User.Read (delegated) permissions.
In my Startup.cs file, I've configured OpenIdConnectAuthenticationNotifications so that I am receiving the access code in AuthorizationCodeReceived. Here is the code:
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType );
app.UseCookieAuthentication(new CookieAuthenticationOptions());
authority = aadInstance + tenantId;
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
ClientId = clientId, Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
TokenResponseReceived = (tr) => { return Task.FromResult(0); },
AuthorizationCodeReceived = (code) => {
// you are here! what's next?
access_code = code.Code;
return Task.FromResult(0);
},
SecurityTokenReceived = (token) =>
{
return Task.FromResult(0);
},
AuthenticationFailed = (context) => { return System.Threading.Tasks.Task.FromResult(0); }
}
});
app.UseStageMarker(PipelineStage.Authenticate);
}
My objective is to call this graph endpoint as the current user to get their JobTitle and > Department from Azure AD. Here is the resource: https://graph.microsoft.com/v1.0/me
I was following this documentation, but it was not clear what to do with the provided access_code. Please help me understand.
Is this access_code a bearer token? can I use it directly to call the graph API?
Do I have to use it to call the /token endpoint to get a bearer token?
Do I have to use it to call the /authorize endpoint to get a bearer token?
I am making direct HTTP requests now, should I use MSAL or Graph SDK?
I think I am trying to accomplish this step:
This is the code I am currently working on, and it returns HTTP CODE 400 (Bad Request):
private void GetOtherProfileData()
{
var cId = Startup.clientId;
var tenantId = Startup.tenantId;
var scope = Startup.scope;
// scope: https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
var code = Startup.access_code;
var redir = HttpUtility.UrlEncode(Startup.redirectUri);
var req_url = $#"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token?client_id={cId}&scope={scope}
&code={code}&redirect_uri={redir}&grant_type=authorization_code
&code_verifier=ThisIsntRandomButItNeedsToBe43CharactersLong";
var req = WebRequest.CreateHttp(req_url);
req.Method = "POST";
req.ContentLength = 0;
req.ContentType = "application/x-www-form-urlencoded";
var resp = req.GetResponse();
var str = resp.GetResponseStream();
var json = new StreamReader(str).ReadToEnd();
Trace.TraceInformation(json);
/// this should return bearer token and then we go call the /me endpoint...
///right?
}
Any code samples or pointers to recent documentation would be helpful.
Is this access_code a bearer token? can I use it directly to call the graph API?
No, code and Access_token are different. You will need a access_token to call Graph API.
Do I have to use it to call the /token endpoint to get a bearer token?
Yes, you'll need code to call token endpoint to get the bearer token.
Do I have to use it to call the /authorize endpoint to get a bearer token?
You will get the code after calling authorize endpoint. You need to pass grant_type=code to get the code in response.
I am making direct HTTP requests now, should I use MSAL or Graph SDK?
You'll need to call Graph API after you get the access_token. Along with the token it also needs proper dedicated and application User permissions from Azure side.
I found a good sample here: https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect
I was trying to do this without a client secret, that was a mistake.
This is how I implemented it =>
AuthorizationCodeReceived = async (context) => {
// you are here!
IConfidentialClientApplication clientApp = MsalAppBuilder.BuildConfidentialClientApplication();
AuthenticationResult result = await clientApp.AcquireTokenByAuthorizationCode(new[] { "User.Read" }, context.Code)
.WithSpaAuthorizationCode() //Request an authcode for the front end
.ExecuteAsync();
access_code = result.AccessToken;
// this is the bearer token.
},
This is what is inside the implementation of BuildConfidentialClientApplication:
clientapp = ConfidentialClientApplicationBuilder.Create(Startup.clientId)
.WithClientSecret(Startup.secret)
.WithRedirectUri(Startup.redirectUri)
.WithAuthority(new Uri(Startup.authority))
.Build();
I am using React as client and Web API core for back end interaction.
For Authentication we are using Token based authentication using AspNet.Security.OpenIdConnect.Server (ASOS).
I have to implement refresh token scenario where on expiration of access token we use refresh token (returned by ASOS) to get new access Token.
I know one way to achieve by calling method on client is in AXIOS interceptor like below.
httpPromise.interceptors.response.use(undefined, err => {
const { config, response: { status } } = err;
const originalRequest = config;
if (status === 401) {
var refresh_Token = JSON.parse(window.localStorage.getItem('refreshToken'));
fetch(globalConstant.WEB_API_BASE_PATH + "authtoken,
{
method: "POST",
headers: new Headers({
'Content-Type': 'application/json',
})
},
data:{grant-type:"refresh_Token",refresh_token:"refresh Token ....."
)
....other logic to set new access token and make call again to existing
request.
}
})
I want to done it in WEB API Core side, so that in middle ware or in authentication pipeline it detects access token expiration and return new access token. The glimpse of WEB API code is like below.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
.... some code
serives.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = OAuthValidationDefaults.AuthenticationScheme;
})
serives.AddOAuthValidation()
serives.AddOpenIdConnectServer(options =>
{
options.ProviderType = typeof(AuthorizationProvider);
options.Provider = new AuthorizationProvider(new SecurityService());
options.TokenEndpointPath = "/authtoken";
options.UserinfoEndpointPath = "/userInfo";
options.AllowInsecureHttp = true;
options.ApplicationCanDisplayErrors = true;
});
..some code
}
The links i followed How to handle expired access token in asp.net core using refresh token with OpenId Connect and https://github.com/mderriey/aspnet-core-token-renewal/blob/master/src/MvcClient/Startup.cs
Using the facebook login authentication in angular app with identity server 4. On logout method PostLogoutRedirectUri , ClientName, LogoutId is always null.
private async Task<LoggedOutViewModel> BuildLoggedOutViewModelAsync(string logoutId)
{
// get context information (client name, post logout redirect URI and iframe for federated signout)
var logout = await _interaction.GetLogoutContextAsync(logoutId);
var vm = new LoggedOutViewModel
{
AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut,
PostLogoutRedirectUri = logout?.PostLogoutRedirectUri,
ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName,
SignOutIframeUrl = logout?.SignOutIFrameUrl,
LogoutId = logoutId
};
if (User?.Identity.IsAuthenticated == true)
{
var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value;
if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider)
{
var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp);
if (providerSupportsSignout)
{
if (vm.LogoutId == null)
{
// if there's no current logout context, we need to create one
// this captures necessary info from the current logged in user
// before we signout and redirect away to the external IdP for signout
vm.LogoutId = await _interaction.CreateLogoutContextAsync();
}
vm.ExternalAuthenticationScheme = idp;
}
}
}
return vm;
}
Angular oidc clident code
logout(): Promise<any> {
return this._userManager.signoutRedirect();
}
Client setup
public IEnumerable<Client> GetClients()
{
var client = new List<Client>
{
new Client
{
ClientId = ConstantValue.ClientId,
ClientName = ConstantValue.ClientName,
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = false,
RedirectUris = { string.Format("{0}/{1}", Configuration["IdentityServerUrls:ClientUrl"], "assets/oidc-login-redirect.html"), string.Format("{0}/{1}", Configuration["IdentityServerUrls:ClientUrl"], "assets/silent-redirect.html") },
PostLogoutRedirectUris = { string.Format("{0}?{1}", Configuration["IdentityServerUrls:ClientUrl"] , "postLogout=true") },
AllowedCorsOrigins = { Configuration["IdentityServerUrls: ClientUrl"] },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
ConstantValue.ClientDashApi
},
IdentityTokenLifetime=120,
AccessTokenLifetime=120
},
};
return client;
}
logoutId is always null. I am successfully able to login to facebook return to the callback method. But redirect uri is always null.
Reference
IdentityServer4 PostLogoutRedirectUri null
This may not be your issue, but it was my issue when I got the same error as you so I am posting my own experience here.
I was following along in a Pluralsight video that was constructing an Angular app using IdentityServer4 as the STS Server, and it directed me to set the post_logout_redirect_uri in the configuration for my UserManager in the AuthService I was constructing, like so:
var config = {
authority: 'http://localhost:4242/',
client_id: 'spa-client',
redirect_uri: `${Constants.clientRoot}assets/oidc-login-redirect.html`,
scope: 'openid projects-api profile',
response_type: 'id_token token',
post_logout_redirect_uri: `${Constants.clientRoot}`,
userStore: new WebStorageStateStore({ store: window.localStorage })
}
this._userManager = new UserManager(config);
An old issue at the github repo https://github.com/IdentityServer/IdentityServer4/issues/396 discussed the fact that this is set automatically now and doesn't need to be set explicitly (see the end of the thread). Once I removed that from the configuration I no longer had the issue where logoutId was null in the AccountController's Logout method:
/// <summary>
/// Show logout page
/// </summary>
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
So this was the correct setup for the config for me:
var config = {
authority: 'http://localhost:4242/',
client_id: 'spa-client',
redirect_uri: `${Constants.clientRoot}assets/oidc-login-redirect.html`,
scope: 'openid projects-api profile',
response_type: 'id_token token',
userStore: new WebStorageStateStore({ store: window.localStorage })
}
this._userManager = new UserManager(config);
I had a similar issue and for a few hours I was lost. In my case the value/url I had in angular for post_logout_redirect_uri (in the UserManagerSettings) was different than the value/url I had in my IdentityServer4 in the field PostLogoutRedirectUris of the Client configuration. I messed up the routes. It's a silly mistake but sometimes you miss the simple things.
I have a Asp.Net Web Api (Api1) that needs to send a message to a Signalr Realtime Api (Api2). I am trying to use Azure AD bearer tokens to authenticate. The client for Api1 is a JavaScript client that uses ADAL.js to get a token from Azure.
var authContext = new AuthenticationContext({
tenant: tenantId,
clientId: jsclientId,
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage',
endpoints: {
api1Url: api1ResourceUri
}
});
authContext.acquireToken(jsclientId, function (error, token) {
if (error || !token) {
authContext.clearCache();
authContext.login();
}
});
The JS client attaches this token in the Authorization header to all Api calls to Api1. In Api1 I am using the following code to get an access token from Azure AD.
var userAssertion = new UserAssertion(bootstrapContext.Token, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
var result = await authenticationContext.AcquireTokenAsync(api2ResourceId, new ClientCredential(api1clientId, api1clientSecret), userAssertion);
I am attaching this access token to the request as an authorization header "Bearer tokenvalue". In the Signalr Hub Owin Startup class I have the following code.
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = api1Audiences,
SaveSigninToken = true
},
Tenant = configSection.TenantId
});
While the ClaimsIdentity on the hub is showing as authenticated, the user's identity is not being set. identity.name is null. It looks like the users identity is not being passed on to the Signalr hub.
You're getting no User Identity in your API because you are authenticating to it as an application, not as a user.
The acquireTokenAsync overload that only takes in resource and ClientCredentials is for the Client Credentials flow (a.k.a App-only flow).
What you need to do is to use the On-behalf-of flow to swap the token you got for API1 for a token for API2.
So in API1's Startup.Auth, TokenValidation parameters, set Save SigninToken to true like so:
app.UseOpenIdConnectAuthentication(
OpenIdConnectAuthenticationOptions
{
// ...
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true
},
// ...
});
And then wherever you want to call your API2, do as follows:
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);
result = await authContext.AcquireTokenAsync(api2ResourceId, clientCred, userAssertion);
See it in the sample: https://github.com/Azure-Samples/active-directory-dotnet-webapi-onbehalfof, specificaly the ToDoService's Startup.Auth.cs and TodoListController.cs.
NOTE: This sample is for native app + web api, you're adapting it for web app + web api.
Edit - Make sure your JS code is requesting a token for your API1 by specifying API1 in the JS acquireToken call like so:
var authContext = new AuthenticationContext({
tenant: tenantId,
clientId: jsclientId,
postLogoutRedirectUri: window.location.origin,
cacheLocation: 'localStorage',
endpoints: {
api1Url: api1ResourceUri
}
});
authContext.acquireToken(api1clientId, function (error, token) {
if (error || !token) {
authContext.clearCache();
authContext.login();
}
});