This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
This repository was archived by the owner on Sep 9, 2022. It is now read-only.

Latest commit

History

335 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Sentinel

TeamCity Build Status

Sentinel is an OAuth server based on the ASP.NET OWIN OAuth 2.0 Authorization Server. This project aims to simplify the work with setting up OAuth on a WebAPI application, by providing you with simpler interfaces and less work to do before you have proper authorization up and running.

Please note that this library is for people wanting to make their own authentication server using ASP.NET. Sentinel is designed to lessen the burden of creating an authentication server, but you need to do some work yourself to fully support OAuth 2.0.

PackageDescriptionVersion
Sentinel.OAuth.CoreThe base package that is used by all the other packages and 3rd party pluginsNuGet Version
Sentinel.OAuthThe authorization provider itselfNuGet Version
Sentinel.OAuth.ClientA generic OAuth client built on the Microsoft HTTP Client LibrariesNuGet Version
Sentinel.OAuth.TokenManagers.RedisA token manager using Redis for storageNuGet Version
Sentinel.OAuth.TokenManagers.RavenDBA token manager using RavenDB for storageNuGet Version
Sentinel.OAuth.TokenManagers.SQLA token manager using SQL for storageNuGet Version

Features

  • Simple setup
  • Supports authorization codes and refresh tokens out of the box
  • Supports basic authentication and signature authentication
  • Easy to extend and configure

Contributing

To make contributions to this project, please fork the develop branch and make your pull request against the develop branch.

Setting up

The easy way

Sentinel needs to know where and how your users and clients are located. This is accomplished by making an implementation of the IUserRepository and IClientRepository interfaces. These have methods that is responsible for locating users and clients, stuff that is probable very specific to your application.

In its simplest form Sentinel the only requires the following code in your OWIN Startup class to work:

app.UseSentinelAuthorizationServer(newSentinelAuthorizationServerOptions(){IssuerUri=newUri("http://my.host"),ClientRepository=newSimpleClientRepository(),UserRepository=newSimpleUserRepository()});

The IUserRepository and a IClientRepository can be implemented like this.

publicclassSimpleUserRepository:IUserRepository{/// <summary>Gets the users.</summary>/// <returns>The users.</returns>publicasyncTask<IEnumerable<IUser>>GetUsers(){returnnewList<IUser>(){newUser(){UserId="myid",Password="some-hash"}};}/// <summary>Gets a user.</summary>/// <param name="userId">Identifier for the user.</param>/// <returns>The user.</returns>publicasyncTask<IUser>GetUser(stringuserId){returnnewUser(){UserId=userId,Password="some-hash"};}}publicclassSimpleClientRepository:IClientRepository{/// <summary>Gets the clients in this collection.</summary>/// <returns>An enumerator that allows foreach to be used to process the clients in this collection.</returns>publicasyncTask<IEnumerable<IClient>>GetClients(){returnnewList<IClient>(){newClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"}};}/// <summary>Gets the client with the specified id.</summary>/// <param name="clientId">Identifier for the client.</param>/// <returns>The client.</returns>publicasyncTask<IClient>GetClient(stringclientId){returnnewClient(){ClientId=clientId,ClientSecret="some-hash",RedirectUri="http://localhost"};}}

Hashing and validation

By default, Sentinel uses HMACSHA-256 for token hashing, and PBKDF2 for password/client secret hashing and validation.
If you use (or want to use) something other than this, you need to swap out the UserManager and ClientManager properties on the configuration object.

Conclusion

The above setup will configure the OAuth server with the default settings, which are as follows:

SettingDefault Value
Access Token Lifetime1 hour
Authorization Code Lifetime5 minutes
Refresh Token Lifetime3 months (90 days)
Token Endpoint/oauth/token
Authorization Code Endpoint/oauth/authorize
UserInfo Endpoint/openid/userinfo
Token FormatJWT (Using a SHA-512 hashing algorithm to encrypt the token)

Notes

You might have noticed the use if ISentinelPrincipal, ISentinelIdentity and SentinelClaim.

  • ISentinelPrincipal is a extension of IPrincipal, the base interface for principals in the .NET world. ClaimsPrincipal also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelPrincipal.
  • ISentinelIdentity is a extension of IIdentity, the base interface for principals in the .NET world. ClaimsIdentity also implements this interface, and the two are convertible via the included extension methods, or in the constructor of ISentinelIdentity.
  • SentinelClaim and its interface ISentinelClaim do not derive from the System.IdentityModel.Claims.Claim. Instead it can take in a Claim in its constructor and can convert back implicitly.

The reason for these custom types are that the built-in ClaimsPrincipal is not PCL-compatible, and the Core and Client packages must be PCL-compatible. I've included a lot of conversion options, so it should not pose a problem for you.

On supporting the authorization_code flow

Sentinel does not include a view for your users to log in when using the /oauth/authorize endpoint. You need to create a page/controller that responds to that endpoint, and that logs in the user using the OWIN AuthorizationManager. However, the Sentinel.OAuth.Authorize package includes a BaseOAuthController class, and it is fairly easy to use:

Comingsoon

About Models

Client

A client has a ClientSecret and a PublicKey property. Both should be populated when creating the client. The ClientSecret should be a hash, and the PublicKey should be the public key portion of the RSA key pair.

User

A user has a Password field that can be used to authenticate the user using Basic authentication. In addition, it is possible to use an api key to authenticate using Basic or Signature authentication.

UserApiKey

A user can have multiple api keys. The private key generated when creating an api key can be used with both Basic and Signature authentication.

Authentication Types

OAuth 2.0 / OpenID Connect

This follows the standard OAuth 2.0 authentication flows.

NOTE: The redirect_uri parameter must be present on all authentication requests.

Basic Authentication

Basic authentication can be enabled by setting the EnableBasicAuthentication property to true when setting up the Authorization server.

When enabled, you can create requests against your API using a regular Basic Authorization header.

Signature Authentication

Signature authentication can be enabled by setting the EnableSignatureAuthentication property to true when setting up the Authorization server. It is safer than Basic authentication, but it is not possible to use it with all application types.

Signature authentication is based on a public/private key system, where the public key is stored at the server. Only the user/client has access to the private key. By default, Sentinel uses SHA256 for hashing, and RSA for generating the private/public key pair. NOTE: The private key must not be disclosed.

To use Signature authentication you must supply an Authorization header using Signature as scheme. To create the parameter you must follow the procedure below:

  1. Create a data string using this format user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce}

     The `timestamp` should be in Unix format
    The `request_url` must match the actual request url
    The `nonce` must be unique in a timeframe of 5 minutes to prevent replay attacks
    
  2. Create a signature for the data string using the private key

  3. Create a digest by adding the signature to the data string

     `user_id={userId},client_id={clientId},redirect_uri={redirectUri},request_url={requestUrl},timestamp={timestamp},nonce={nonce},signature={signature}` 
  4. Base64 encode the digest

  5. Add the digest to the Authorization header

     Authorization: Signature dXNlcl9pZD1OVW5pdCxjbGllbnRfaWQ9TlVuaXQscmVkaXJlY3RfdXJpPWh0dHA6Ly9sb2NhbGhvc3QscmVxdWVzdF91cmw9b3BlbmlkL3VzZXJpbmZvLHRpbWVzdGFtcD0xNDc2Njk5NTQzLG5vbmNlPTQwNmQ5OTk3ODNjYTQwZGZiMzY4YzQxNzkzZTAzMmEyLHNpZ25hdHVyZT1HR1VNZE1CSTFNR2x4cFhGZENkcUcvZkFpUkRzWnJ2aGF6NDN2MUJ1TUduS29zZ2FwU0Z0dml4ZU14RCtGcFZlblBoTExGSVlJSnFMbHZWVGF0V2U2UT09
    

Usage

There is nothing special with Sentinel as an OAuth 2 provider, you can use a normal OAuth client that conforms to the specification.
Sentinel also includes a client for use in .NET projects (source)

There is one thing that must be mentioned however. Sentinel requires the client redirect uri parameter to be present on the authorize request. Not all OAuth 2 providers do this, but it is recommended according to the specification.

Performance

These are the average performance results for the included storage providers in the current version.
Please note that these tests may not be fair. The tests are equal, but the connection is not. In addition, I currently do not have a lot of statistics history so the averages might be off by quite a lot.

Also, you must not discard the idea that some methods need to be optimized :-)
The Authenticate methods for the Redis provider are too slow and should be made much faster.

ActionMemorySQL (LocalDb)RedisRavenDB
Create Authorization Code442ms21ms477ms488ms
Authenticate Authorization Code445ms24ms702ms594ms
Create Access Token451ms23ms486ms468ms
Authenticate Access Token464ms60ms6244ms1004ms
Create Refresh Token433ms4ms472ms447ms
Authenticate Refresh Token460ms47ms3424ms613ms

Extending

The samples below can be mixed and matched to your liking. You can use SQL Server for storing users and clients, and then use RavenDB for storing tokens, or the other way around :-)

Custom OAuth Grant Types

TODO: Example using custom grant type to add a new property to the token response

Custom User Manager

It is possible to use the ASP.NET Identity system to store your users and use Sentinel at the same time.
Please look at the sample implementation in the AspNetIdentityUserManager project

There is also a demo using Dapper and a vanilla SQL database in the SqlServerUserManager project.

Custom Client Manager

There is a sample implementation using Dapper and SQL Server here. You can also use NoSQL databases for storing clients.

Custom Token Manager

Guides on how to use token managers with persistent storage.

Claims

There are some claims that will be added to your user principal that are specific for Sentinel.
Below you can find an overview of claims with explanations.

ClaimExplanation
urn:oauth:clientThe client that was used to authenticate the user
urn:oauth:scopeThe scope that was set when asking for an authorization code or access token
urn:oauth:accesstokenThe access token for the current user object
urn:oauth:refreshtokenThe refresh token for the current user object

TODO (Roadmap)

  • Add support for scope handling

About

An OAuth server based on the OWIN OAuth 2.0 Authorization Server

Resources

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages