I am trying to authenticate my app using ADFS and oauth2. I found a lot of documentation to do this with an azure service (using ADAL). But there is no info about how to do it with a local server.
I tested all the info below with an angular app and the authentication works!
public class AuthenticationService
{
public static string clientId = "uri:tst-amdm-website.mycompany.be";
private static string commonAuthority = "https://claim.mycompany.be/";
public static Uri returnUri = new Uri("http://www.google.be");
const string graphResourceUri = "uri:tst-amdm-api.mycompany.be";
public async void GetAccessToken(IPlatformParameters platformParameters)
{
AuthenticationResult authResult = null;
JObject jResult = null;
//List<User> results = new List<User>();
try
{
AuthenticationContext authContext = new AuthenticationContext(commonAuthority);
if (authContext.TokenCache.ReadItems().Any())
authContext = new AuthenticationContext(authContext.TokenCache.ReadItems().First().Authority);
authResult = await authContext.AcquireTokenAsync(graphResourceUri, clientId, returnUri, platformParameters);
var test = authResult.AccessToken;
}
catch (Exception ee)
{
//results.Add(new User { error = ee.Message });
//return results;
}
}
}
This is the error I get, but in angular this url: https://claim.mycompany.be/ works perfectly.
'authority' Uri should have at least one segment in the path (i.e. https://<host>/<path>/...)
There's good references here but note that you need ADFS 4.0 to do this.
For ADFS 3.0. your choices are limited. Good overview here.
Related
I am making an application using AWS cognito and Spring Boot. After registering, users confirm their account by email or SMS activation code. After they confirm their account, can I do an automatic session login? Can I start a session without a password only for confirmation cases?
Yes, you can perform login for the user without a password using Custom Authentication Flow.
You will have to add Lambda Triggers to handle your custom auth flow.
In application, you will have to use AdminInitiateAuth API call.
Here is some code example to understand the general idea:
public void auth(String username) {
AwsBasicCredentials awsCreds = AwsBasicCredentials.create(AWS_KEY,
AWS_SECRET);
CognitoIdentityProviderClient identityProviderClient =
CognitoIdentityProviderClient.builder()
.credentialsProvider(StaticCredentialsProvider.create(awsCreds))
.region(Region.of(REGION))
.build();
final Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", username);
authParams.put("SECRET_HASH", calculateSecretHash(CLIENT_ID,
CLIENT_SECRET, username));
final AdminInitiateAuthRequest authRequest = AdminInitiateAuthRequest.builder()
.authFlow(AuthFlowType.CUSTOM_AUTH)
.clientId(CLIENT_ID)
.userPoolId(POOL_ID)
.authParameters(authParams)
.build();
AdminInitiateAuthResponse result = identityProviderClient.adminInitiateAuth(authRequest);
System.out.println(result.authenticationResult().accessToken());
System.out.println(result.authenticationResult().idToken());
}
private String calculateSecretHash(String userPoolClientId, String userPoolClientSecret, String userName) {
final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
SecretKeySpec signingKey = new SecretKeySpec(
userPoolClientSecret.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256_ALGORITHM);
try {
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(signingKey);
mac.update(userName.getBytes(StandardCharsets.UTF_8));
byte[] rawHmac = mac.doFinal(userPoolClientId.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(rawHmac);
} catch (Exception e) {
throw new RuntimeException("Error while calculating ");
}
}
You will also need to add dependecies for AWS SDK:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-core</artifactId>
<version>2.13.57</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cognitoidentityprovider</artifactId>
<version>2.13.57</version>
</dependency>
And add Lambda for "Define Auth Challange" trigger of you user pool:
exports.handler = async (event) => {
// Don't do any checks just say that authentication is successfull
event.response.issueTokens = true;
event.response.failAuthentication = false;
return event;
};
The answer of Yuriy P is perfect.
Additionally, in the case Cognito's client application does not set for password, you can ignore "SECRET_HASH"
The code is simplifier as below:
public AdminInitiateAuthResult adminInitiateAuth(String account, String clientId) {
final Map<String, String> authParams = new HashMap<>();
authParams.put("USERNAME", account);
final AdminInitiateAuthRequest authRequest = new AdminInitiateAuthRequest();
authRequest.setAuthFlow(AuthFlowType.CUSTOM_AUTH);
authRequest.setClientId(clientId);
authRequest.setUserPoolId(poolId);
authRequest.setAuthParameters(authParams);
AdminInitiateAuthResult result = cognitoIdentityClient.adminInitiateAuth(authRequest);
System.out.println("accessToken:" + result.getAuthenticationResult().getAccessToken());
System.out.println("idToken:" +result.getAuthenticationResult().getIdToken());
return result;
}
I'm trying to access Google Drive API using a service account. I had set up the service account to access Google Directory API and I assume I can use the same for this. Here is my code, it's pretty much self-explanatory.
private static List<File> retrieveAllFiles(Drive service) throws IOException {
List<File> result = new ArrayList<File>();
Files.List request = service.files().list();
do {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} while (request.getPageToken() != null &&
request.getPageToken().length() > 0);
return result;
}
public static Drive getDriveService(){
GoogleDriectoryService GDservice = new GoogleDriectoryService();
java.io.File serviceAccountP12Certificate = GDservice.loadF("resources/APIProject-62d885cdf671.p12");
HttpTransport httpTransport = new NetHttpTransport();//?
JacksonFactory jsonFactory = new JacksonFactory();//?
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(serviceAccountEmailAddress)
.setServiceAccountScopes(Arrays.asList(DriveScopes.DRIVE))
.setServiceAccountUser(serviceAccountUser)
.setServiceAccountPrivateKeyFromP12File(serviceAccountP12Certificate)
.build();
Drive service = new Drive.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("radiantlogic-vds").build();
return service;
}
public static void main(String[] args){
Drive service = getDriveService();
List<File> files = retrieveAllFiles(service);
System.out.println(files);
}
But, I'm getting "com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized" at request.execute() line. Any help is appreciated.
Im getting an infinite redirect after i logged in with ADFS 2.0.
My ConfigureAuth.cs is like
//defines default authentication to WSFederation
app.SetDefaultSignInAsAuthenticationType(WsFederationAuthenticationDefaults.AuthenticationType);
//Defines the MetadataAddress and realm
app.UseWsFederationAuthentication(new WsFederationAuthenticationOptions
{
MetadataAddress = ConfigurationManager.AppSettings["ida:AdfsMetadataEndpoint"],
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"]
});
//Defines WSFederation cookie as default authentication type
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
});
i can get to the ADFS login page, but when it returns to my app it keeps asking ADFS for a valid authentication, after 6 requests i get blocked by ADFS.
UPDATE 1
It turns out i needed to specify the Issuer, TokenEndpoint and the certificate key, for some reason owin didnt get these values from the metadata, so i ended up copying the values of the metadata and using them in the webconfig under appsettings.
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
Configuration = getWsFederationConfiguration()
}
);
}
private static WsFederationConfiguration getWsFederationConfiguration()
{
WsFederationConfiguration configuration = new WsFederationConfiguration
{
Issuer = ConfigurationManager.AppSettings["wsFederation:trustedIssuer"],
TokenEndpoint = ConfigurationManager.AppSettings["wsFederation:issuer"],
};
configuration.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(ConfigurationManager.AppSettings["wsFederation:trustedIssuerSigningKey"]))));
return configuration;
}
How do you trigger authentication? If it is through an [Authorize], do you happen to request special user or roles? If you request a role that the signed in user does not have, you'll end up bouncing around.
Also, you should change the order of your calls: first set the cookie middleware, then the protocol one.
It turns out i needed to specify the Issuer, TokenEndpoint and the certificate key, for some reason owin didnt get these values from the metadata, so i ended up copying the values of the metadata and using them in the webconfig under appsettings.
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions { });
app.UseWsFederationAuthentication(
new WsFederationAuthenticationOptions
{
Wtrealm = ConfigurationManager.AppSettings["ida:Audience"],
AuthenticationType = WsFederationAuthenticationDefaults.AuthenticationType,
Configuration = getWsFederationConfiguration()
}
);
}
private static WsFederationConfiguration getWsFederationConfiguration()
{
WsFederationConfiguration configuration = new WsFederationConfiguration
{
Issuer = ConfigurationManager.AppSettings["wsFederation:trustedIssuer"],
TokenEndpoint = ConfigurationManager.AppSettings["wsFederation:issuer"],
};
configuration.SigningKeys.Add(new X509SecurityKey(new X509Certificate2(Convert.FromBase64String(ConfigurationManager.AppSettings["wsFederation:trustedIssuerSigningKey"]))));
return configuration;
}
I am creating a simple authentication server using the default owin oauth server. After supplying the correct credentials a bearer token is generated and returned to the client. I used among others this tutorial by Taiseer
I would like to store the token in a database before the token is send to the client.
Maybe I completely overlooked it, but where can I get the token before it is send? As far as I know the token is generated after the ticket is validated in the GrantResourceOwnerCredentials method.
I am guessing the token is stored in the context. How can I get it out?
Startup.cs
private void ConfigureAuthServer(IAppBuilder app) {
// Configure the application for OAuth based flow
var oAuthServerOptions = new OAuthAuthorizationServerOptions {
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
Provider = new ApplicationOAuthProvider(),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14)
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
ApplicationOAuthProvider
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context) {
//Dummy check here
if (context.UserName != context.Password) {
context.SetError("invalid_grant", "The user name or password is incorrect");
return Task.FromResult<object>(null);
}
var claims = new List<Claim> {
new Claim(ClaimTypes.NameIdentifier, context.UserName),
new Claim(ClaimTypes.Name, context.UserName)
};
var oAuthIdentity = new ClaimsIdentity(claims, OAuthDefaults.AuthenticationType);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
context.Validated(ticket);
return Task.FromResult<object>(null);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context) {
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) {
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
Note: for those who wonder why I want to store the tokens.. it is a requirement I have to fulfill.
To fetch the token before it is sent to the client you must override TokenEndpointResponse:
public override Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
{
return base.TokenEndpointResponse(context);
}
the context object has a property AccessToken which will contains the representation of the token as a string.
OAuthTokenEndpointResponseContext contains a dictionary of objects
IDictionary<string, object> in AdditionalResponseParameters which allows us to find all the claims for the indentity.
If we wanted to fetch the expiration of the token we would find the claim .expires in the dictionary:
context.AdditionalResponseParameters[".expires"]
There's a github repository if someone is interested to play with a simple integration of client and server interaction.
let me first set the stage with the cast of characters:
An MVC 5 application
A WebAPI
I need to implement a token based security to access #2 from #1.
What I have already:
Created a startup class in #2
public void Configuration(IAppBuilder app)
{
// token generation
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
Provider = new MyServerProvider()
});
// token consumption
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
var httpConfiguration = new HttpConfiguration();
WebApiConfig.Register(new HttpConfiguration());
app.UseWebApi(httpConfiguration);
}
Standard code available everywhere on the internet.
This is the code in MyServerProvider also in #2
public class MyServerProvider: OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
await Task.FromResult(0);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
if (context.UserName == "one" && context.Password == "two")
{
var id = new ClaimsIdentity(context.Options.AuthenticationType);
id.AddClaim(new Claim("name", context.UserName));
id.AddClaim(new Claim("role", "user"));
context.Validated(id);
}
else
{
context.Rejected();
}
await Task.FromResult(0);
}
}
And another class that provides the token also in #3
public class TokenProvider
{
public TokenResponse _tokenValue { get; set; }
public string _accessToken { get; set; }
public string GetToken(string tokenEndpoint, string userName, string password)
{
var client = new OAuth2Client(new Uri(tokenEndpoint));
var tokenResponse = client.RequestResourceOwnerPasswordAsync(userName, userName).Result;
_tokenValue = tokenResponse;
_accessToken = _tokenValue.AccessToken;
return _accessToken;
}
}
So far so good.
Q1. Now when a request from a controller hits the api or the api is
called form JavaScript, what happens?
Q2. Which method from the
above get's called?
Q3. What does GrantResourceOwnerCredentials do?
Q4. What does the context object in the above question has and how
does one add the userName and Password to it and how are claims store in a cookie?
Q5. If I have to store the token in a cookie and use it for subsequent requests, do I write
that code in OnActionExecuting method of the controller in #1?
This all may sound very specific but it isn't. I am trying to understand token based authentication from a real world scenario and I am new to this.
I have gone through the samples at ThinkTecture Github repo and they all do a good job in explaining them but I am stuck at implementing it.
Any help is greatly appreciated.
Regards.