I am currently generating SAML tokens from ADFS like this:
WSTrustChannelFactory factory = null;
try
{
// use a UserName Trust Binding for username authentication
factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress("https://adfs.company.com/adfs/services/trust/13/usernamemixed"));
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = "user";
factory.Credentials.UserName.Password = "pw";
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(relyingPartyId),
KeyType = KeyTypes.Bearer
};
IWSTrustChannelContract channel = factory.CreateChannel();
GenericXmlSecurityToken genericToken = channel.Issue(rst)
as GenericXmlSecurityToken;
}
finally
{
if (factory != null)
{
try
{
factory.Close();
}
catch (CommunicationObjectFaultedException)
{
factory.Abort();
}
}
}
Now let's say I build a web application that uses these tokens for authentication. As far as I know the workflow should be like this:
Generate token
client gets generated token (after valid login)
client caches token
client uses token for next login
web application validates token, does not have to call ADFS
How can I validate that the token the client presents is valid? Do I need the certificate of the ADFS server to decrypt the token?
After looking at the excellent thinktecture identity server code ( https://github.com/thinktecture/Thinktecture.IdentityServer.v2/tree/master/src/Libraries/Thinktecture.IdentityServer.Protocols/AdfsIntegration ) I extracted the solution:
using Newtonsoft.Json;
using System;
using System.IdentityModel.Protocols.WSTrust;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;
using Thinktecture.IdentityModel.Extensions;
using Thinktecture.IdentityModel.WSTrust;
namespace SimpleWebConsole
{
internal class ADFS
{
public static void tokenTest()
{
string relyingPartyId = "https://party.mycomp.com";
WSTrustChannelFactory factory = null;
try
{
// use a UserName Trust Binding for username authentication
factory = new WSTrustChannelFactory(
new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
new EndpointAddress("https://adfs.mycomp.com/adfs/services/trust/13/usernamemixed"));
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = "test";
factory.Credentials.UserName.Password = "test";
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(relyingPartyId),
KeyType = KeyTypes.Bearer
};
IWSTrustChannelContract channel = factory.CreateChannel();
GenericXmlSecurityToken genericToken = channel.Issue(rst) as GenericXmlSecurityToken; //MessageSecurityException -> PW falsch
var _handler = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
var tokenString = genericToken.ToTokenXmlString();
var samlToken2 = _handler.ReadToken(new XmlTextReader(new StringReader(tokenString)));
ValidateSamlToken(samlToken2);
X509Certificate2 certificate = null;
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
certificate = store.Certificates.Find(X509FindType.FindByThumbprint, "thumb", false)[0];
var jwt=ConvertSamlToJwt(samlToken2, "https://party.mycomp.com", certificate);
}
finally
{
if (factory != null)
{
try
{
factory.Close();
}
catch (CommunicationObjectFaultedException)
{
factory.Abort();
}
}
}
}
public static TokenResponse ConvertSamlToJwt(SecurityToken securityToken, string scope, X509Certificate2 SigningCertificate)
{
var subject = ValidateSamlToken(securityToken);
var descriptor = new SecurityTokenDescriptor
{
Subject = subject,
AppliesToAddress = scope,
SigningCredentials = new X509SigningCredentials(SigningCertificate),
TokenIssuerName = "https://panav.mycomp.com",
Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddMinutes(10080))
};
var jwtHandler = new JwtSecurityTokenHandler();
var jwt = jwtHandler.CreateToken(descriptor);
return new TokenResponse
{
AccessToken = jwtHandler.WriteToken(jwt),
ExpiresIn = 10080
};
}
public static ClaimsIdentity ValidateSamlToken(SecurityToken securityToken)
{
var configuration = new SecurityTokenHandlerConfiguration();
configuration.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
configuration.CertificateValidationMode = X509CertificateValidationMode.None;
configuration.RevocationMode = X509RevocationMode.NoCheck;
configuration.CertificateValidator = X509CertificateValidator.None;
var registry = new ConfigurationBasedIssuerNameRegistry();
registry.AddTrustedIssuer("thumb", "ADFS Signing - mycomp.com");
configuration.IssuerNameRegistry = registry;
var handler = SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection(configuration);
var identity = handler.ValidateToken(securityToken).First();
return identity;
}
public class TokenResponse
{
[JsonProperty(PropertyName = "access_token")]
public string AccessToken { get; set; }
[JsonProperty(PropertyName = "token_type")]
public string TokenType { get; set; }
[JsonProperty(PropertyName = "expires_in")]
public int ExpiresIn { get; set; }
[JsonProperty(PropertyName = "refresh_token")]
public string RefreshToken { get; set; }
}
}
}
It's much simpler! For web sites you use WIF (assuming you are using .NET) and then you federate the app with ADFS. (There's a wizard included in the WIF SDK). Everything is taken care of. Parsing, validation, etc. is done by the framework. Your app would deal with users in the regular way: this.User.Name , this.User.IsInRole("admin"), etc.
The scenario is documented here.
Related
I am trying to create a C# version of a JavaScript Amazon Cognito user pool authentication (see here) but it does not work. The response always shows null. Please find code below:
using System;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
namespace ConsoleApp1
{
class AmazonCognitoSetup
{
private AuthFlowResponse response;
public AuthFlowResponse Response { get; set; }
public async void AsyncStuff()
{
String userpool_id = "us-west-2_NqkuZcXQY";
String client_id = "4l9rvl4mv5es1eep1qe97cautn";
String username = "username"
String password = "password"
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USWest2);
var userpool = new CognitoUserPool(userpool_id, client_id, provider);
var user = new CognitoUser(username, client_id, userpool, provider);
InitiateSrpAuthRequest initiateSrpAuthRequest = new() { Password = password};
Console.WriteLine("Getting credentials");
response = await user.StartWithSrpAuthAsync(initiateSrpAuthRequest).ConfigureAwait(false);//shows null
var accesstoken = response.AuthenticationResult.AccessToken;
Console.WriteLine(accesstoken);
}
}
}
Fixed it. The issue was that the authentication was asynchronous so I had to find a way to block until the response came back. See redone code below:
using System;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class AmazonCognitoSetup
{
private string userpool_id = "us-west-2_NqkuZcXQY";
private string client_id = "4l9rvl4mv5es1eep1qe97cautn";
private string username = "username";
private string password = "password";
private string idToken;
private string refreshToken;
private string accessToken;
public string IdToken { get => idToken; set => idToken = value; }
public string RefreshToken { get => refreshToken; set => refreshToken = value; }
public string AccessToken { get => accessToken; set => accessToken = value; }
public void AsyncStuff()
{
//FileMaker PRO credentials for Amazon
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), Amazon.RegionEndpoint.USWest2);
var userpool = new CognitoUserPool(userpool_id, client_id, provider);
var user = new CognitoUser(username, client_id, userpool, provider);
InitiateSrpAuthRequest initiateSrpAuthRequest = new() {
Password = password
};
//authenticate to get tokens <--- change was here
var task = Task.Run<AuthFlowResponse>(async()=> await user.StartWithSrpAuthAsync(initiateSrpAuthRequest));
//assign tokens from results
this.idToken = task.Result.AuthenticationResult.IdToken;
this.refreshToken = task.Result.AuthenticationResult.RefreshToken;
this.accessToken = task.Result.AuthenticationResult.AccessToken;
}
}
}
I'm trying to create an online meeting from ASP.NET Core Web API, but I'm getting this "generalException" error.
AuthenticationProvider for configuring auth for the API call:
Code:
public class GraphAuthenticationProvider : IAuthenticationProvider
{
public const string GRAPH_URI = "https://graph.microsoft.com/v1.0/";
/// <summary>
/// Default constructor
/// </summary>
/// <param name="configuration"></param>
public GraphAuthenticationProvider(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; set; }
public async Task AuthenticateRequestAsync(HttpRequestMessage request)
{
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{Configuration["AzureAd:TenantId"]}");
ClientCredential credentials = new ClientCredential(Configuration["AzureAd:ClientId"], Configuration["AzureAd:ClientSecret"]);
AuthenticationResult authResult = await authContext.AcquireTokenAsync(GRAPH_URI, credentials);
request.Headers.Add("Authorization", "Bearer " + authResult.AccessToken);
request.Headers.Add("Content-type", "application/json");
}
}
GraphProvider for making the actual API request:
public class MicrosoftGraphProvider : IGraphProvider
{
private IGraphServiceClient _graph;
public MicrosoftGraphProvider(IGraphServiceClient graph)
{
_graph = graph;
}
public async Task<CreateMeetingResult> CreateMeetingAsync(OnlineMeeting onlineMeeting)
{
// Initialize error message
var errorMessage = default(string);
// Initialize meeting result
var meetingResult = default(OnlineMeeting);
try
{
// Try creating the meeting
meetingResult = await _graph.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting);
}
catch (Exception ex)
{
// Set the error message
errorMessage = ex.Message;
}
// Return the result
return new CreateMeetingResult
{
ErrorPhrase = errorMessage,
MeetingResult = meetingResult
};
}
}
AuthenticationProvider, GraphServiceClient, and GraphProvider transient instances in StartUp.cs:
services.AddTransient<IAuthenticationProvider, GraphAuthenticationProvider>(provider =>
new GraphAuthenticationProvider(provider.GetService<IConfiguration>()));
// Add transient instance of the graph service client
services.AddTransient<IGraphServiceClient, GraphServiceClient>(provider =>
new GraphServiceClient(provider.GetService<IAuthenticationProvider>()));
// Add transient instance of the graph provider
services.AddTransient<IGraphProvider, MicrosoftGraphProvider>(provider =>
new MicrosoftGraphProvider(provider.GetService<IGraphServiceClient>()));
Setting OnlineMeeting data and invoking CreatingMeeting:
var onlineMeeting = new OnlineMeeting
{
Subject = meetingDetails.Subject,
AllowedPresenters = OnlineMeetingPresenters.Organizer,
IsEntryExitAnnounced = true,
Participants = new MeetingParticipants
{
Attendees = new List<MeetingParticipantInfo>
{
new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Attendee,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "Attendee1"
}
}
},
new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Presenter,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "Attendee2"
}
}
},
new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Presenter,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = "Attendee3"
}
}
}
},
Organizer = new MeetingParticipantInfo
{
Role = OnlineMeetingRole.Presenter,
Identity = new IdentitySet
{
Application = new Identity
{
Id = Guid.NewGuid().ToString(),
DisplayName = Framework.Construction.Configuration["OnlineMeeting:Organiser:DisplayName"]
}
}
}
},
EndDateTime = DateTimeOffset.Now.AddHours(1),
StartDateTime = DateTimeOffset.Now.AddHours(2)
};
// Fire create meeting
var meetingResult = await _graphProvider.CreateMeetingAsync(onlineMeeting);
ApiResponse:
{
"response": null,
"successful": false,
"errorMessage": "Code: generalException\r\nMessage: An error occurred sending the request.\r\n"
}
I have created an application in azure app service and added all necessary permissions as well.
What is it that I'm not doing correctly?
If you require more info, please ask me.
Thanks in advance.
I'm not sure where the error happened, but there are some problems in your code.
As I said in the comment, the resource should be https://graph.microsoft.com/. https://graph.microsoft.com/v1.0/ is the URL of Microsoft Graph API. Please see this article.
Here are some examples of Microsoft web-hosted resources:
Microsoft Graph: https://graph.microsoft.com
Microsoft 365 Mail API: https://outlook.office.com
Azure Key Vault: https://vault.azure.net
Try to replace Me with Users[<user-id>].
You use ClientCredential to authorize, which means you are using the Client credential flows. The OAuth 2.0 client credentials grant flow permits a web service (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling another web service. So you could not call Microsoft Graph API with .Me which is used for the signed-in user.
Using this approach I can get auth token for my application on home/resource ADFS as well as on partner/users. Also I have claims aware WCF service of my own. It's configured to work with home/resource ADFS. Naturally, it accepts tokens from home/resource ADFS and rejects from partner/users ADFS.
I can make my WCF service trust tokens issued by partner/users ADFS but it seems wrong from architectural point of view. Somehow I should get token from home/resource ADFS using established trust between home/resource ADFS and partner/users ADFS.
Therefore I have to either 1) issue token on home/resource ADFS using token from partner/users ADFS or 2) somehow authorize user from partner/users AD directly on home/resource ADFS using his login and password. Only active authentication scenarios are considered.
Could you help me with samples of code to solve the first or/and the second problem?
I've been looking for solution for two weeks and finally found the answer. Correct flow is the flow number 1 where you authorize partner's user against partner's ADFS and then use received token to authorize it against resource ADFS. The working code sample is following
using System;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using System.ServiceModel.Security;
using Microsoft.IdentityModel.Protocols.WSTrust;
using WSTrustChannel = Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannel;
using WSTrustChannelFactory = Microsoft.IdentityModel.Protocols.WSTrust.WSTrustChannelFactory;
namespace SOS.Tools.AdfsConnectionChecker
{
/// <summary> Parameters to get token from partner's ADFS
/// using his domain user credentials </summary>
public class AdfsIdentityProviderTokenRequest
{
/// <summary>Domain user from identity provider domain.
/// E.g. Name.FamalyName#ipDomainName.local or ipDomain2kName\Name.FamalyName </summary>
public string IpDomainUserName { get; set; }
/// <summary>Domain user password</summary>
public string IpDomainUserPassword { get; set; }
/// <summary> Identyty provider token issuer server, i.e your partner adfs server.
/// E.g. adfs.partnerdomain.com</summary>
public string IpTokenIssuerServer { get; set; }
/// <summary>Resources token issuer server, i.e. your company adsf server.
/// E.g. adfs.resourcedomain.com</summary>
public string ResourcesTokenIssuerServer { get; set; }
}
/// <summary> Parameters to get token for your application from resource ADFS
/// using token from partner's ADFS</summary>
public class AdfsResourceProviderTokenForAppRequest
{
/// <summary>Token recieved from identity provider. </summary>
public SecurityToken IpToken { get; set; }
/// <summary>Resources token issuer server, i.e. your company adsf server.
/// E.g. adfs.resourcedomain.com</summary>
public string ResourcesTokenIssuerServer { get; set; }
/// <summary>Apllication you want token for. In terms of ADFS its Relying Party.
/// E.g. https://myAppServer.MyAppDomain.com/MyWcfService1 </summary>
public string AppUrl { get; set; }
}
public class AdfsTokenFactory
{
public SecurityToken GetIpToken(AdfsIdentityProviderTokenRequest request)
{
//string username, string password, string tokenIssuer, string appliesTo, out
var binding = new WS2007HttpBinding();
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
var tokenIssuerUrlFormat = "https://{0}/adfs/services/trust/13/usernamemixed";
var tokenIssuerUrl = string.Format(tokenIssuerUrlFormat, request.IpTokenIssuerServer);
using (var factory = new WSTrustChannelFactory(binding, new EndpointAddress(tokenIssuerUrl)))
{
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.UserName.UserName = request.IpDomainUserName;
factory.Credentials.UserName.Password = request.IpDomainUserPassword;
factory.ConfigureChannelFactory();
var trustUrlFromat = "http://{0}/adfs/services/trust";
var trustUrl = string.Format(trustUrlFromat, request.ResourcesTokenIssuerServer);
var requestToken = new RequestSecurityToken(WSTrust13Constants.RequestTypes.Issue);
requestToken.AppliesTo = new EndpointAddress(trustUrl);
var tokenClient = (WSTrustChannel) factory.CreateChannel();
RequestSecurityTokenResponse rsts;
var token = tokenClient.Issue(requestToken, out rsts);
return token;
}
}
public SecurityToken GetResourceTokenForApp(AdfsResourceProviderTokenForAppRequest request)
{
var binding = new WS2007FederationHttpBinding();
binding.Security.Message.IssuedKeyType = SecurityKeyType.SymmetricKey;
binding.Security.Message.EstablishSecurityContext = false;
binding.Security.Mode = WSFederationHttpSecurityMode.TransportWithMessageCredential;
var tokenIssuerUrlFormat = "https://{0}/adfs/services/trust/13/IssuedTokenMixedSymmetricBasic256";
var tokenIssuerUrl = string.Format(tokenIssuerUrlFormat, request.ResourcesTokenIssuerServer);
using (var factory = new WSTrustChannelFactory(binding, new EndpointAddress(new Uri(tokenIssuerUrl))))
{
var rst = new RequestSecurityToken
{
RequestType = WSTrust13Constants.RequestTypes.Issue,
AppliesTo = new EndpointAddress(request.AppUrl),
KeyType = WSTrust13Constants.KeyTypes.Symmetric,
};
factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
factory.TrustVersion = TrustVersion.WSTrust13;
factory.Credentials.SupportInteractive = false;
factory.ConfigureChannelFactory();
var channel = factory.CreateChannelWithIssuedToken(request.IpToken);
RequestSecurityTokenResponse rstr;
SecurityToken token = channel.Issue(rst, out rstr);
return token;
}
}
}
}
You call it like this
var factory = new AdfsTokenFactory();
var ipTokenRequest = new AdfsIdentityProviderTokenRequest
{
//or "ipDomain2kName\Name.FamalyName"
IpDomainUserName = "Name.FamalyName#ipDomainName.local",
IpDomainUserPassword = "userDomainPassword",
IpTokenIssuerServer = "adfs.partnerdomain.com",
ResourcesTokenIssuerServer = "adfs.resourcedomain.com"
};
var ipToken = factory.GetIpToken(ipTokenRequest);
var appTokenRequest = new AdfsResourceProviderTokenForAppRequest
{
IpToken = ipToken,
ResourcesTokenIssuerServer ="adfs.resourcedomain.com",
AppUrl = "https://myAppServer.MyAppDomain.com/MyWcfService1"
};
var appToken = factory.GetResourceTokenForApp(appTokenRequest);
Ok, so this one has been highly frustrating and I cannot seem to figure out how to fix it, hoping someone can point me in the right direction.
So if I may give a run down of the architecture:
Solution
IdentityServer Layer
WebApi Layer
MVC layer (as the client)
The Issue
When trying to access the user that has been authenticated by the IdentityServer it always returns false
if (this.User.Identity.IsAuthenticated)
{
var identity = this.User.Identity as ClaimsIdentity;
foreach (var claim in identity.Claims)
{
Debug.Write(claim.Type + " " + claim.Value);
}
}
I've spent many hours trying to figure why I do not have access to the user.
IdentityServer
public void Configuration(IAppBuilder app)
{
app.Map("/identity", idsrvApp =>
{
var idServerServiceFactory = new IdentityServerServiceFactory()
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get())
.UseInMemoryUsers(Users.Get());
var options = new IdentityServerOptions
{
Factory = idServerServiceFactory,
SiteName = "Proderty Security Token Service",
IssuerUri = "https://localhost:44300/identity",
PublicOrigin = "https://localhost:44300/",
SigningCertificate = LoadCertificate()
};
idsrvApp.UseIdentityServer(options);
});
}
public static IEnumerable<Client> Get()
{
return new[]
{
new Client()
{
ClientId = "prodertyclientcredentials",
ClientName = "Proderty (Client Credentials)",
Flow = Flows.ClientCredentials,
ClientSecrets = new List<Secret>()
{
new Secret("Some Secret here".Sha256())
},
AllowAccessToAllScopes = true
},
new Client()
{
ClientId = "prodertyauthcode",
ClientName = "Proderty (Authorization Code)",
Flow = Flows.AuthorizationCode,
RedirectUris = new List<string>()
{
"http://localhost:10765/prodertycallback"
},
ClientSecrets = new List<Secret>()
{
new Secret("Some Secret here".Sha256())
},
AllowAccessToAllScopes = true
},
new Client()
{
ClientId = "prodertyhybrid",
ClientName = "Proderty (Hybrid)",
Flow = Flows.Hybrid,
RedirectUris = new List<string>()
{
"http://localhost:10765"
},
AllowAccessToAllScopes = true
}
};
}
public static IEnumerable<Scope> Get()
{
return new List<Scope>()
{
new Scope
{
Name = "prodertymanagment",
DisplayName = "Proderty Management",
Description = "Allow the application to manage proderty on your behalf.",
Type = ScopeType.Resource
},
StandardScopes.OpenId,
StandardScopes.Profile
};
}
return new List<InMemoryUser>()
{
new InMemoryUser()
{
Username = "Terry",
Password = "Bob",
Subject = "b053d546-6ca8-b95c-77e94d705ddf",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "Terry"),
new Claim(Constants.ClaimTypes.FamilyName, "Wingfield")
}
},
new InMemoryUser()
{
Username = "John",
Password = "Bob",
Subject = "bb61e881-3a49-8b62-c13dbe102018",
Claims = new[]
{
new Claim(Constants.ClaimTypes.GivenName, "John"),
new Claim(Constants.ClaimTypes.FamilyName, "Borris")
}
}
};
As you can see the IdentityServer seems positively intact. Once called I expect that I should be access the user on the MVC client.
WebApi
public void Configuration(IAppBuilder app)
{
app.UseIdentityServerBearerTokenAuthentication(
new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost:44300/identity",
RequiredScopes = new[] { "prodertymanagment" }
});
}
[Authorize]
public class TestController : ApiController
{
// GET api/<controller>
public string GetOne()
{
return "If you can see this, then we must be authorized! - GETONE - Coming From Test Controller";
}}
}
MVCClient
public static HttpClient GetClient()
{
var client = new HttpClient();
var accessToken = Token.RequestAccessTokenAuthorizationCode();
if (accessToken != null)
{
client.SetBearerToken(accessToken);
}
client.BaseAddress = new Uri("https://localhost:44301/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
public static string RequestAccessTokenAuthorizationCode()
{
var cookie = HttpContext.Current.Request.Cookies.Get("ProdertyCookie");
if (cookie != null && cookie["access_token"] != null)
{
return cookie["access_token"];
}
var authorizeRequest = new IdentityModel.Client.AuthorizeRequest(
"https://localhost:44300/identity/connect/authorize"
);
var state = HttpContext.Current.Request.Url.OriginalString;
var url = authorizeRequest.CreateAuthorizeUrl(
"prodertyauthcode",
"code",
"prodertymanagment",
"http://localhost:10765/prodertycallback",
state);
HttpContext.Current.Response.Redirect(url);
return null;
}
// GET: ProdertyCallback
public async Task<ActionResult> Index()
{
var authCode = Request.QueryString["code"];
var client = new TokenClient(
"https://localhost:44300/identity/connect/token",
"prodertyauthcode",
"Some Secret here"
);
var tokenResponse = await client.RequestAuthorizationCodeAsync(
authCode,
"http://localhost:10765/prodertycallback"
);
Response.Cookies["ProdertyCookie"]["access_token"] = tokenResponse.AccessToken;
return Redirect(Request.QueryString["state"]);
}
With the code above everything works, If not logged in I'm pushed to the IdentityServer login page, My callback fires and then returned to the originating page. I also have a access token.
Now that I'm authorized to access the page, I expect to have access to the user but I don't which is highly frustrating.
I'm very aware that it's the app that's being authenticated but I'm sure the purpose was to access the user that authenticated the app (As a user of the client system too).
public async Task<ActionResult> Index()
{
if (this.User.Identity.IsAuthenticated)
{
var identity = this.User.Identity as ClaimsIdentity;
foreach (var claim in identity.Claims)
{
Debug.Write(claim.Type + " " + claim.Value);
}
}
var httpClient = ProdertyHttpClient.GetClient();
var test = await httpClient.GetAsync("api/test/getone").ConfigureAwait(false);
if (test.IsSuccessStatusCode)
{
var stringContent = await test.Content.ReadAsStringAsync().ConfigureAwait(false);
return Content(stringContent);
}
else
{
return Content(ExceptionHelper.GetExceptionFromResponse(test).ToString());
}
}
The only thing that springs to mind is using the Authorize Attribute which attempts to use the core asp.net identity model which I have not install because I'll have a custom data store.
Any help greatly appreciated!
I'm quite new to Web API and i still don't get it how to request n JWT.
In My Startup.cs i configured OAuth and my custom JWT:
CustomJwtFormat.cs
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string issuer = string.Empty;
private readonly int timeoutMinutes = 60;
public CustomJwtFormat(string issuer)
{
this.issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audience = "all";
var secret = Convert.FromBase64String("mySecret");
var now = DateTime.UtcNow;
var expires = now.AddMinutes(timeoutMinutes);
string signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256";
string digestAlgorithm = "http://www.w3.org/2001/04/xmlenc#sha256";
var signingKey = new SigningCredentials(
new InMemorySymmetricSecurityKey(secret), signatureAlgorithm, digestAlgorithm);
var token = new JwtSecurityToken(issuer, audience, data.Identity.Claims,
now, expires, signingKey);
var tokenString = new JwtSecurityTokenHandler().WriteToken(token);
return tokenString;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
EDIT: I managed retrieving a token with Postman.
Now my next problem: as long as i send a valid jwt everything works ok. But whenn i send e.g. 'bearer 12345' i get the error message
IDX10708: 'System.IdentityModel.Tokens.JwtSecurityTokenHandler' cannot read this string: '12345'.
...in Visual Studio. Where do i need to put in the token validation? Make a new class and put the TokenSecurityHandler anywhere?